Suppression for crbug/241044.
[chromium-blink-merge.git] / chrome / test / chromedriver / chrome_launcher.cc
blob6484ba26d94bb8bc444b424566bca7ae7de05bff
1 // Copyright (c) 2013 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.
5 #include "chrome/test/chromedriver/chrome_launcher.h"
7 #include "base/base64.h"
8 #include "base/command_line.h"
9 #include "base/file_util.h"
10 #include "base/files/file_path.h"
11 #include "base/format_macros.h"
12 #include "base/json/json_reader.h"
13 #include "base/json/json_writer.h"
14 #include "base/process.h"
15 #include "base/process_util.h"
16 #include "base/string_util.h"
17 #include "base/stringprintf.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_split.h"
20 #include "base/threading/platform_thread.h"
21 #include "base/time.h"
22 #include "base/utf_string_conversions.h"
23 #include "base/values.h"
24 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
25 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
26 #include "chrome/test/chromedriver/chrome/chrome_finder.h"
27 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
28 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
29 #include "chrome/test/chromedriver/chrome/status.h"
30 #include "chrome/test/chromedriver/chrome/user_data_dir.h"
31 #include "chrome/test/chromedriver/chrome/version.h"
32 #include "chrome/test/chromedriver/chrome/zip.h"
33 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
35 namespace {
37 Status UnpackAutomationExtension(const base::FilePath& temp_dir,
38 base::FilePath* automation_extension) {
39 std::string decoded_extension;
40 if (!base::Base64Decode(kAutomationExtension, &decoded_extension))
41 return Status(kUnknownError, "failed to base64decode automation extension");
43 base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip");
44 int size = static_cast<int>(decoded_extension.length());
45 if (file_util::WriteFile(extension_zip, decoded_extension.c_str(), size)
46 != size) {
47 return Status(kUnknownError, "failed to write automation extension zip");
50 base::FilePath extension_dir = temp_dir.AppendASCII("internal");
51 if (!zip::Unzip(extension_zip, extension_dir))
52 return Status(kUnknownError, "failed to unzip automation extension");
54 *automation_extension = extension_dir;
55 return Status(kOk);
58 Status PrepareCommandLine(int port,
59 const Capabilities& capabilities,
60 CommandLine* prepared_command,
61 base::ScopedTempDir* user_data_dir,
62 base::ScopedTempDir* extension_dir) {
63 CommandLine command = capabilities.command;
64 base::FilePath program = command.GetProgram();
65 if (program.empty()) {
66 if (!FindChrome(&program))
67 return Status(kUnknownError, "cannot find Chrome binary");
68 command.SetProgram(program);
69 } else if (!file_util::PathExists(program)) {
70 return Status(kUnknownError,
71 base::StringPrintf("no chrome binary at %" PRFilePath,
72 program.value().c_str()));
75 command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port));
76 command.AppendSwitch("no-first-run");
77 command.AppendSwitch("enable-logging");
78 command.AppendSwitchASCII("logging-level", "1");
79 command.AppendArg("data:text/html;charset=utf-8,");
81 if (!command.HasSwitch("user-data-dir")) {
82 if (!user_data_dir->CreateUniqueTempDir())
83 return Status(kUnknownError, "cannot create temp dir for user data dir");
84 command.AppendSwitchPath("user-data-dir", user_data_dir->path());
85 Status status = internal::PrepareUserDataDir(
86 user_data_dir->path(), capabilities.prefs.get(),
87 capabilities.local_state.get());
88 if (status.IsError())
89 return status;
92 if (!extension_dir->CreateUniqueTempDir()) {
93 return Status(kUnknownError,
94 "cannot create temp dir for unpacking extensions");
96 Status status = internal::ProcessExtensions(
97 capabilities.extensions, extension_dir->path(), true, &command);
98 if (status.IsError())
99 return status;
101 *prepared_command = command;
102 return Status(kOk);
105 Status ParseAndCheckVersion(const std::string& devtools_version,
106 std::string* version,
107 int* build_no) {
108 if (devtools_version.empty()) {
109 // Content Shell has an empty product version and a fake user agent.
110 // There's no way to detect the actual version, so assume it is tip of tree.
111 *version = "content shell";
112 *build_no = 9999;
113 return Status(kOk);
115 std::string prefix = "Chrome/";
116 if (devtools_version.find(prefix) != 0u) {
117 return Status(kUnknownError,
118 "unrecognized Chrome version: " + devtools_version);
121 std::string stripped_version = devtools_version.substr(prefix.length());
122 int temp_build_no;
123 std::vector<std::string> version_parts;
124 base::SplitString(stripped_version, '.', &version_parts);
125 if (version_parts.size() != 4 ||
126 !base::StringToInt(version_parts[2], &temp_build_no)) {
127 return Status(kUnknownError,
128 "unrecognized Chrome version: " + devtools_version);
131 if (temp_build_no < kMinimumSupportedChromeBuildNo) {
132 return Status(kUnknownError, "Chrome version must be >= " +
133 GetMinimumSupportedChromeVersion());
135 *version = stripped_version;
136 *build_no = temp_build_no;
137 return Status(kOk);
140 Status WaitForDevToolsAndCheckVersion(
141 int port,
142 URLRequestContextGetter* context_getter,
143 const SyncWebSocketFactory& socket_factory,
144 scoped_ptr<DevToolsHttpClient>* user_client,
145 std::string* version,
146 int* build_no) {
147 scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient(
148 port, context_getter, socket_factory));
150 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20);
151 std::string devtools_version;
152 Status status(kOk);
153 while (base::Time::Now() < deadline) {
154 status = client->GetVersion(&devtools_version);
155 if (status.IsOk())
156 break;
157 if (status.code() != kChromeNotReachable)
158 return status;
159 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
161 if (status.IsError())
162 return status;
163 status = ParseAndCheckVersion(devtools_version, version, build_no);
164 if (status.IsError())
165 return status;
167 while (base::Time::Now() < deadline) {
168 WebViewsInfo views_info;
169 client->GetWebViewsInfo(&views_info);
170 if (views_info.GetSize()) {
171 *user_client = client.Pass();
172 return Status(kOk);
174 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
176 return Status(kUnknownError, "unable to discover open pages");
179 Status LaunchDesktopChrome(
180 URLRequestContextGetter* context_getter,
181 int port,
182 const SyncWebSocketFactory& socket_factory,
183 const Capabilities& capabilities,
184 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
185 scoped_ptr<Chrome>* chrome) {
186 CommandLine command(CommandLine::NO_PROGRAM);
187 base::ScopedTempDir user_data_dir;
188 base::ScopedTempDir extension_dir;
189 PrepareCommandLine(port, capabilities,
190 &command, &user_data_dir, &extension_dir);
191 command.AppendSwitch("ignore-certificate-errors");
192 base::LaunchOptions options;
194 #if !defined(OS_WIN)
195 base::EnvironmentVector environ;
196 if (!capabilities.log_path.empty()) {
197 environ.push_back(
198 base::EnvironmentVector::value_type("CHROME_LOG_FILE",
199 capabilities.log_path));
200 options.environ = &environ;
202 #endif
204 LOG(INFO) << "Launching chrome: " << command.GetCommandLineString();
205 base::ProcessHandle process;
206 if (!base::LaunchProcess(command, options, &process))
207 return Status(kUnknownError, "chrome failed to start");
209 scoped_ptr<DevToolsHttpClient> devtools_client;
210 std::string version;
211 int build_no;
212 Status status = WaitForDevToolsAndCheckVersion(
213 port, context_getter, socket_factory, &devtools_client, &version,
214 &build_no);
216 if (status.IsError()) {
217 int exit_code;
218 base::TerminationStatus chrome_status =
219 base::GetTerminationStatus(process, &exit_code);
220 if (chrome_status != base::TERMINATION_STATUS_STILL_RUNNING) {
221 std::string termination_reason;
222 switch (chrome_status) {
223 case base::TERMINATION_STATUS_NORMAL_TERMINATION:
224 termination_reason = "exited normally";
225 break;
226 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
227 termination_reason = "exited abnormally";
228 break;
229 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
230 termination_reason = "was killed";
231 break;
232 case base::TERMINATION_STATUS_PROCESS_CRASHED:
233 termination_reason = "crashed";
234 break;
235 default:
236 termination_reason = "unknown";
237 break;
239 return Status(kUnknownError,
240 "Chrome failed to start: " + termination_reason);
242 if (!base::KillProcess(process, 0, true)) {
243 int exit_code;
244 if (base::GetTerminationStatus(process, &exit_code) ==
245 base::TERMINATION_STATUS_STILL_RUNNING)
246 return Status(kUnknownError, "cannot kill Chrome", status);
248 return status;
250 chrome->reset(new ChromeDesktopImpl(
251 devtools_client.Pass(), version, build_no, devtools_event_listeners,
252 process, &user_data_dir, &extension_dir));
253 return Status(kOk);
256 Status LaunchAndroidChrome(
257 URLRequestContextGetter* context_getter,
258 int port,
259 const SyncWebSocketFactory& socket_factory,
260 const Capabilities& capabilities,
261 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
262 scoped_ptr<Chrome>* chrome) {
263 // TODO(frankf): Figure out how this should be installed to
264 // make this work for all platforms.
265 base::FilePath adb_commands(FILE_PATH_LITERAL("adb_commands.py"));
266 CommandLine command(adb_commands);
267 command.AppendSwitchASCII("package", capabilities.android_package);
268 command.AppendSwitch("launch");
269 command.AppendSwitchASCII("port", base::IntToString(port));
271 std::string output;
272 if (!base::GetAppOutput(command, &output)) {
273 if (output.empty())
274 return Status(
275 kUnknownError,
276 "failed to run adb_commands.py. Make sure it is set in PATH.");
277 else
278 return Status(kUnknownError, "android app failed to start.\n" + output);
281 scoped_ptr<DevToolsHttpClient> devtools_client;
282 std::string version;
283 int build_no;
284 Status status = WaitForDevToolsAndCheckVersion(
285 port, context_getter, socket_factory, &devtools_client, &version,
286 &build_no);
287 if (status.IsError())
288 return status;
290 chrome->reset(new ChromeAndroidImpl(
291 devtools_client.Pass(), version, build_no, devtools_event_listeners));
292 return Status(kOk);
295 } // namespace
297 Status LaunchChrome(
298 URLRequestContextGetter* context_getter,
299 int port,
300 const SyncWebSocketFactory& socket_factory,
301 const Capabilities& capabilities,
302 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
303 scoped_ptr<Chrome>* chrome) {
304 if (capabilities.IsAndroid()) {
305 return LaunchAndroidChrome(
306 context_getter, port, socket_factory, capabilities,
307 devtools_event_listeners, chrome);
308 } else {
309 return LaunchDesktopChrome(
310 context_getter, port, socket_factory, capabilities,
311 devtools_event_listeners, chrome);
315 namespace internal {
317 Status ProcessExtensions(const std::vector<std::string>& extensions,
318 const base::FilePath& temp_dir,
319 bool include_automation_extension,
320 CommandLine* command) {
321 std::vector<base::FilePath::StringType> extension_paths;
322 size_t count = 0;
323 for (std::vector<std::string>::const_iterator it = extensions.begin();
324 it != extensions.end(); ++it) {
325 std::string extension_base64;
326 // Decodes extension string.
327 // Some WebDriver client base64 encoders follow RFC 1521, which require that
328 // 'encoded lines be no more than 76 characters long'. Just remove any
329 // newlines.
330 RemoveChars(*it, "\n", &extension_base64);
331 std::string decoded_extension;
332 if (!base::Base64Decode(extension_base64, &decoded_extension))
333 return Status(kUnknownError, "failed to base64 decode extension");
335 // Writes decoded extension into a temporary .crx file.
336 base::ScopedTempDir temp_crx_dir;
337 if (!temp_crx_dir.CreateUniqueTempDir())
338 return Status(kUnknownError,
339 "cannot create temp dir for writing extension CRX file");
340 base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
341 int size = static_cast<int>(decoded_extension.length());
342 if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size)
343 != size) {
344 return Status(kUnknownError, "failed to write extension file");
347 // Unzips the temporary .crx file.
348 count++;
349 base::FilePath extension_dir = temp_dir.AppendASCII(
350 base::StringPrintf("extension%" PRIuS, count));
351 if (!zip::Unzip(extension_crx, extension_dir))
352 return Status(kUnknownError, "failed to unzip the extension CRX file");
353 extension_paths.push_back(extension_dir.value());
356 if (include_automation_extension) {
357 base::FilePath automation_extension;
358 Status status = UnpackAutomationExtension(temp_dir, &automation_extension);
359 if (status.IsError())
360 return status;
361 extension_paths.push_back(automation_extension.value());
364 if (extension_paths.size()) {
365 base::FilePath::StringType extension_paths_value = JoinString(
366 extension_paths, FILE_PATH_LITERAL(','));
367 command->AppendSwitchNative("load-extension", extension_paths_value);
369 return Status(kOk);
372 Status WritePrefsFile(
373 const std::string& template_string,
374 const base::DictionaryValue* custom_prefs,
375 const base::FilePath& path) {
376 int code;
377 std::string error_msg;
378 scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError(
379 template_string, 0, &code, &error_msg));
380 base::DictionaryValue* prefs;
381 if (!template_value || !template_value->GetAsDictionary(&prefs)) {
382 return Status(kUnknownError,
383 "cannot parse internal JSON template: " + error_msg);
386 if (custom_prefs) {
387 for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd();
388 it.Advance()) {
389 prefs->Set(it.key(), it.value().DeepCopy());
393 std::string prefs_str;
394 base::JSONWriter::Write(prefs, &prefs_str);
395 if (static_cast<int>(prefs_str.length()) != file_util::WriteFile(
396 path, prefs_str.c_str(), prefs_str.length())) {
397 return Status(kUnknownError, "failed to write prefs file");
399 return Status(kOk);
402 Status PrepareUserDataDir(
403 const base::FilePath& user_data_dir,
404 const base::DictionaryValue* custom_prefs,
405 const base::DictionaryValue* custom_local_state) {
406 base::FilePath default_dir = user_data_dir.AppendASCII("Default");
407 if (!file_util::CreateDirectory(default_dir))
408 return Status(kUnknownError, "cannot create default profile directory");
410 Status status = WritePrefsFile(
411 kPreferences,
412 custom_prefs,
413 default_dir.AppendASCII("Preferences"));
414 if (status.IsError())
415 return status;
417 status = WritePrefsFile(
418 kLocalState,
419 custom_local_state,
420 user_data_dir.AppendASCII("Local State"));
421 if (status.IsError())
422 return status;
424 // Write empty "First Run" file, otherwise Chrome will wipe the default
425 // profile that was written.
426 if (file_util::WriteFile(
427 user_data_dir.AppendASCII("First Run"), "", 0) != 0) {
428 return Status(kUnknownError, "failed to write first run file");
430 return Status(kOk);
433 } // namespace internal