OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/test/chromedriver/chrome_launcher.h" | 5 #include "chrome/test/chromedriver/chrome_launcher.h" |
6 | 6 |
7 #include "base/base64.h" | 7 #include "base/base64.h" |
8 #include "base/command_line.h" | 8 #include "base/command_line.h" |
9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
10 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
49 | 49 |
50 base::FilePath extension_dir = temp_dir.AppendASCII("internal"); | 50 base::FilePath extension_dir = temp_dir.AppendASCII("internal"); |
51 if (!zip::Unzip(extension_zip, extension_dir)) | 51 if (!zip::Unzip(extension_zip, extension_dir)) |
52 return Status(kUnknownError, "failed to unzip automation extension"); | 52 return Status(kUnknownError, "failed to unzip automation extension"); |
53 | 53 |
54 *automation_extension = extension_dir; | 54 *automation_extension = extension_dir; |
55 return Status(kOk); | 55 return Status(kOk); |
56 } | 56 } |
57 | 57 |
58 Status PrepareCommandLine(int port, | 58 Status PrepareCommandLine(int port, |
59 const base::FilePath& exe, | 59 const Capabilities& capabilities, |
60 const base::ListValue* args, | |
61 const base::ListValue* extensions, | |
62 const base::DictionaryValue* prefs, | |
63 const base::DictionaryValue* local_state, | |
64 CommandLine* prepared_command, | 60 CommandLine* prepared_command, |
65 base::ScopedTempDir* user_data_dir, | 61 base::ScopedTempDir* user_data_dir, |
66 base::ScopedTempDir* extension_dir) { | 62 base::ScopedTempDir* extension_dir) { |
67 base::FilePath program = exe; | 63 CommandLine command = capabilities.command; |
| 64 base::FilePath program = command.GetProgram(); |
68 if (program.empty()) { | 65 if (program.empty()) { |
69 if (!FindChrome(&program)) | 66 if (!FindChrome(&program)) |
70 return Status(kUnknownError, "cannot find Chrome binary"); | 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())); |
71 } | 73 } |
72 LOG(INFO) << "Using chrome from " << program.value(); | 74 LOG(INFO) << "Using chrome from " << program.value(); |
73 | 75 |
74 CommandLine command(program); | |
75 command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port)); | 76 command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port)); |
76 command.AppendSwitch("no-first-run"); | 77 command.AppendSwitch("no-first-run"); |
77 command.AppendSwitch("enable-logging"); | 78 command.AppendSwitch("enable-logging"); |
78 command.AppendSwitchASCII("logging-level", "1"); | 79 command.AppendSwitchASCII("logging-level", "1"); |
79 command.AppendArg("data:text/html;charset=utf-8,"); | 80 command.AppendArg("data:text/html;charset=utf-8,"); |
80 | 81 |
81 if (args) { | |
82 Status status = internal::ProcessCommandLineArgs(args, &command); | |
83 if (status.IsError()) | |
84 return status; | |
85 } | |
86 | |
87 if (!command.HasSwitch("user-data-dir")) { | 82 if (!command.HasSwitch("user-data-dir")) { |
88 if (!user_data_dir->CreateUniqueTempDir()) | 83 if (!user_data_dir->CreateUniqueTempDir()) |
89 return Status(kUnknownError, "cannot create temp dir for user data dir"); | 84 return Status(kUnknownError, "cannot create temp dir for user data dir"); |
90 command.AppendSwitchPath("user-data-dir", user_data_dir->path()); | 85 command.AppendSwitchPath("user-data-dir", user_data_dir->path()); |
91 Status status = internal::PrepareUserDataDir( | 86 Status status = internal::PrepareUserDataDir( |
92 user_data_dir->path(), prefs, local_state); | 87 user_data_dir->path(), capabilities.prefs.get(), |
| 88 capabilities.local_state.get()); |
93 if (status.IsError()) | 89 if (status.IsError()) |
94 return status; | 90 return status; |
95 } | 91 } |
96 | 92 |
97 if (!extension_dir->CreateUniqueTempDir()) { | 93 if (!extension_dir->CreateUniqueTempDir()) { |
98 return Status(kUnknownError, | 94 return Status(kUnknownError, |
99 "cannot create temp dir for unpacking extensions"); | 95 "cannot create temp dir for unpacking extensions"); |
100 } | 96 } |
101 Status status = internal::ProcessExtensions( | 97 Status status = internal::ProcessExtensions( |
102 extensions, extension_dir->path(), true, &command); | 98 capabilities.extensions, extension_dir->path(), true, &command); |
103 if (status.IsError()) | 99 if (status.IsError()) |
104 return status; | 100 return status; |
105 | 101 |
106 *prepared_command = command; | 102 *prepared_command = command; |
107 return Status(kOk); | 103 return Status(kOk); |
108 } | 104 } |
109 | 105 |
110 Status ParseAndCheckVersion(const std::string& devtools_version, | 106 Status ParseAndCheckVersion(const std::string& devtools_version, |
111 std::string* version, | 107 std::string* version, |
112 int* build_no) { | 108 int* build_no) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
174 client->GetWebViewsInfo(&views_info); | 170 client->GetWebViewsInfo(&views_info); |
175 if (views_info.GetSize()) { | 171 if (views_info.GetSize()) { |
176 *user_client = client.Pass(); | 172 *user_client = client.Pass(); |
177 return Status(kOk); | 173 return Status(kOk); |
178 } | 174 } |
179 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); | 175 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
180 } | 176 } |
181 return Status(kUnknownError, "unable to discover open pages"); | 177 return Status(kUnknownError, "unable to discover open pages"); |
182 } | 178 } |
183 | 179 |
184 } // namespace | |
185 | |
186 Status LaunchDesktopChrome(URLRequestContextGetter* context_getter, | 180 Status LaunchDesktopChrome(URLRequestContextGetter* context_getter, |
187 int port, | 181 int port, |
188 const SyncWebSocketFactory& socket_factory, | 182 const SyncWebSocketFactory& socket_factory, |
189 const base::FilePath& exe, | 183 const Capabilities& capabilities, |
190 const base::ListValue* args, | |
191 const base::ListValue* extensions, | |
192 const base::DictionaryValue* prefs, | |
193 const base::DictionaryValue* local_state, | |
194 const std::string& log_path, | |
195 scoped_ptr<Chrome>* chrome) { | 184 scoped_ptr<Chrome>* chrome) { |
196 CommandLine command(CommandLine::NO_PROGRAM); | 185 CommandLine command(CommandLine::NO_PROGRAM); |
197 base::ScopedTempDir user_data_dir; | 186 base::ScopedTempDir user_data_dir; |
198 base::ScopedTempDir extension_dir; | 187 base::ScopedTempDir extension_dir; |
199 PrepareCommandLine(port, exe, args, extensions, prefs, local_state, | 188 PrepareCommandLine(port, capabilities, |
200 &command, &user_data_dir, &extension_dir); | 189 &command, &user_data_dir, &extension_dir); |
201 base::LaunchOptions options; | 190 base::LaunchOptions options; |
202 | 191 |
203 #if !defined(OS_WIN) | 192 #if !defined(OS_WIN) |
204 base::EnvironmentVector environ; | 193 base::EnvironmentVector environ; |
205 if (!log_path.empty()) { | 194 if (!capabilities.log_path.empty()) { |
206 environ.push_back(base::EnvironmentVector::value_type("CHROME_LOG_FILE", | 195 environ.push_back( |
207 log_path)); | 196 base::EnvironmentVector::value_type("CHROME_LOG_FILE", |
| 197 capabilities.log_path)); |
208 options.environ = &environ; | 198 options.environ = &environ; |
209 } | 199 } |
210 #endif | 200 #endif |
211 | 201 |
212 base::ProcessHandle process; | 202 base::ProcessHandle process; |
213 if (!base::LaunchProcess(command, options, &process)) | 203 if (!base::LaunchProcess(command, options, &process)) |
214 return Status(kUnknownError, "chrome failed to start"); | 204 return Status(kUnknownError, "chrome failed to start"); |
215 | 205 |
216 scoped_ptr<DevToolsHttpClient> devtools_client; | 206 scoped_ptr<DevToolsHttpClient> devtools_client; |
217 std::string version; | 207 std::string version; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
256 } | 246 } |
257 chrome->reset(new ChromeDesktopImpl( | 247 chrome->reset(new ChromeDesktopImpl( |
258 devtools_client.Pass(), version, build_no, process, &user_data_dir, | 248 devtools_client.Pass(), version, build_no, process, &user_data_dir, |
259 &extension_dir)); | 249 &extension_dir)); |
260 return Status(kOk); | 250 return Status(kOk); |
261 } | 251 } |
262 | 252 |
263 Status LaunchAndroidChrome(URLRequestContextGetter* context_getter, | 253 Status LaunchAndroidChrome(URLRequestContextGetter* context_getter, |
264 int port, | 254 int port, |
265 const SyncWebSocketFactory& socket_factory, | 255 const SyncWebSocketFactory& socket_factory, |
266 const std::string& package_name, | 256 const Capabilities& capabilities, |
267 scoped_ptr<Chrome>* chrome) { | 257 scoped_ptr<Chrome>* chrome) { |
268 // TODO(frankf): Figure out how this should be installed to | 258 // TODO(frankf): Figure out how this should be installed to |
269 // make this work for all platforms. | 259 // make this work for all platforms. |
270 base::FilePath adb_commands(FILE_PATH_LITERAL("adb_commands.py")); | 260 base::FilePath adb_commands(FILE_PATH_LITERAL("adb_commands.py")); |
271 CommandLine command(adb_commands); | 261 CommandLine command(adb_commands); |
272 command.AppendSwitchASCII("package", package_name); | 262 command.AppendSwitchASCII("package", capabilities.android_package); |
273 command.AppendSwitch("launch"); | 263 command.AppendSwitch("launch"); |
274 command.AppendSwitchASCII("port", base::IntToString(port)); | 264 command.AppendSwitchASCII("port", base::IntToString(port)); |
275 | 265 |
276 std::string output; | 266 std::string output; |
277 if (!base::GetAppOutput(command, &output)) { | 267 if (!base::GetAppOutput(command, &output)) { |
278 if (output.empty()) | 268 if (output.empty()) |
279 return Status( | 269 return Status( |
280 kUnknownError, | 270 kUnknownError, |
281 "failed to run adb_commands.py. Make sure it is set in PATH."); | 271 "failed to run adb_commands.py. Make sure it is set in PATH."); |
282 else | 272 else |
283 return Status(kUnknownError, "android app failed to start.\n" + output); | 273 return Status(kUnknownError, "android app failed to start.\n" + output); |
284 } | 274 } |
285 | 275 |
286 scoped_ptr<DevToolsHttpClient> devtools_client; | 276 scoped_ptr<DevToolsHttpClient> devtools_client; |
287 std::string version; | 277 std::string version; |
288 int build_no; | 278 int build_no; |
289 Status status = WaitForDevToolsAndCheckVersion( | 279 Status status = WaitForDevToolsAndCheckVersion( |
290 port, context_getter, socket_factory, &devtools_client, &version, | 280 port, context_getter, socket_factory, &devtools_client, &version, |
291 &build_no); | 281 &build_no); |
292 if (status.IsError()) | 282 if (status.IsError()) |
293 return status; | 283 return status; |
294 | 284 |
295 chrome->reset(new ChromeAndroidImpl( | 285 chrome->reset(new ChromeAndroidImpl( |
296 devtools_client.Pass(), version, build_no)); | 286 devtools_client.Pass(), version, build_no)); |
297 return Status(kOk); | 287 return Status(kOk); |
298 } | 288 } |
299 | 289 |
| 290 } // namespace |
| 291 |
| 292 Status LaunchChrome(URLRequestContextGetter* context_getter, |
| 293 int port, |
| 294 const SyncWebSocketFactory& socket_factory, |
| 295 const Capabilities& capabilities, |
| 296 scoped_ptr<Chrome>* chrome) { |
| 297 if (capabilities.IsAndroid()) { |
| 298 return LaunchAndroidChrome( |
| 299 context_getter, port, socket_factory, capabilities, chrome); |
| 300 } else { |
| 301 return LaunchDesktopChrome( |
| 302 context_getter, port, socket_factory, capabilities, chrome); |
| 303 } |
| 304 } |
| 305 |
300 namespace internal { | 306 namespace internal { |
301 | 307 |
302 Status ProcessCommandLineArgs(const base::ListValue* args, | 308 Status ProcessExtensions(const std::vector<std::string>& extensions, |
303 CommandLine* command) { | |
304 for (size_t i = 0; i < args->GetSize(); ++i) { | |
305 std::string arg_string; | |
306 if (!args->GetString(i, &arg_string)) | |
307 return Status(kUnknownError, "invalid chrome command line argument"); | |
308 size_t separator_index = arg_string.find("="); | |
309 if (separator_index != std::string::npos) { | |
310 CommandLine::StringType arg_string_native; | |
311 if (!args->GetString(i, &arg_string_native)) | |
312 return Status(kUnknownError, "invalid chrome command line argument"); | |
313 command->AppendSwitchNative( | |
314 arg_string.substr(0, separator_index), | |
315 arg_string_native.substr(separator_index + 1)); | |
316 } else { | |
317 command->AppendSwitch(arg_string); | |
318 } | |
319 } | |
320 return Status(kOk); | |
321 } | |
322 | |
323 Status ProcessExtensions(const base::ListValue* extensions, | |
324 const base::FilePath& temp_dir, | 309 const base::FilePath& temp_dir, |
325 bool include_automation_extension, | 310 bool include_automation_extension, |
326 CommandLine* command) { | 311 CommandLine* command) { |
327 std::vector<base::FilePath::StringType> extension_paths; | 312 std::vector<base::FilePath::StringType> extension_paths; |
328 for (size_t i = 0; i < (extensions ? extensions->GetSize() : 0); ++i) { | 313 size_t count = 0; |
| 314 for (std::vector<std::string>::const_iterator it = extensions.begin(); |
| 315 it != extensions.end(); ++it) { |
329 std::string extension_base64; | 316 std::string extension_base64; |
330 if (!extensions->GetString(i, &extension_base64)) { | |
331 return Status(kUnknownError, | |
332 "each extension must be a base64 encoded string"); | |
333 } | |
334 | |
335 // Decodes extension string. | 317 // Decodes extension string. |
336 // Some WebDriver client base64 encoders follow RFC 1521, which require that | 318 // Some WebDriver client base64 encoders follow RFC 1521, which require that |
337 // 'encoded lines be no more than 76 characters long'. Just remove any | 319 // 'encoded lines be no more than 76 characters long'. Just remove any |
338 // newlines. | 320 // newlines. |
339 RemoveChars(extension_base64, "\n", &extension_base64); | 321 RemoveChars(*it, "\n", &extension_base64); |
340 std::string decoded_extension; | 322 std::string decoded_extension; |
341 if (!base::Base64Decode(extension_base64, &decoded_extension)) | 323 if (!base::Base64Decode(extension_base64, &decoded_extension)) |
342 return Status(kUnknownError, "failed to base64 decode extension"); | 324 return Status(kUnknownError, "failed to base64 decode extension"); |
343 | 325 |
344 // Writes decoded extension into a temporary .crx file. | 326 // Writes decoded extension into a temporary .crx file. |
345 base::ScopedTempDir temp_crx_dir; | 327 base::ScopedTempDir temp_crx_dir; |
346 if (!temp_crx_dir.CreateUniqueTempDir()) | 328 if (!temp_crx_dir.CreateUniqueTempDir()) |
347 return Status(kUnknownError, | 329 return Status(kUnknownError, |
348 "cannot create temp dir for writing extension CRX file"); | 330 "cannot create temp dir for writing extension CRX file"); |
349 base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); | 331 base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); |
350 int size = static_cast<int>(decoded_extension.length()); | 332 int size = static_cast<int>(decoded_extension.length()); |
351 if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) | 333 if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) |
352 != size) | 334 != size) { |
353 return Status(kUnknownError, "failed to write extension file"); | 335 return Status(kUnknownError, "failed to write extension file"); |
| 336 } |
354 | 337 |
355 // Unzips the temporary .crx file. | 338 // Unzips the temporary .crx file. |
| 339 count++; |
356 base::FilePath extension_dir = temp_dir.AppendASCII( | 340 base::FilePath extension_dir = temp_dir.AppendASCII( |
357 base::StringPrintf("extension%" PRIuS, i)); | 341 base::StringPrintf("extension%" PRIuS, count)); |
358 if (!zip::Unzip(extension_crx, extension_dir)) | 342 if (!zip::Unzip(extension_crx, extension_dir)) |
359 return Status(kUnknownError, "failed to unzip the extension CRX file"); | 343 return Status(kUnknownError, "failed to unzip the extension CRX file"); |
360 extension_paths.push_back(extension_dir.value()); | 344 extension_paths.push_back(extension_dir.value()); |
361 } | 345 } |
362 | 346 |
363 if (include_automation_extension) { | 347 if (include_automation_extension) { |
364 base::FilePath automation_extension; | 348 base::FilePath automation_extension; |
365 Status status = UnpackAutomationExtension(temp_dir, &automation_extension); | 349 Status status = UnpackAutomationExtension(temp_dir, &automation_extension); |
366 if (status.IsError()) | 350 if (status.IsError()) |
367 return status; | 351 return status; |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
427 // Write empty "First Run" file, otherwise Chrome will wipe the default | 411 // Write empty "First Run" file, otherwise Chrome will wipe the default |
428 // profile that was written. | 412 // profile that was written. |
429 if (file_util::WriteFile( | 413 if (file_util::WriteFile( |
430 user_data_dir.AppendASCII("First Run"), "", 0) != 0) { | 414 user_data_dir.AppendASCII("First Run"), "", 0) != 0) { |
431 return Status(kUnknownError, "failed to write first run file"); | 415 return Status(kUnknownError, "failed to write first run file"); |
432 } | 416 } |
433 return Status(kOk); | 417 return Status(kOk); |
434 } | 418 } |
435 | 419 |
436 } // namespace internal | 420 } // namespace internal |
OLD | NEW |