android_webview: explicitly use gcc for target binaries.
[chromium-blink-merge.git] / chrome / test / chromedriver / chrome_launcher.cc
blob21595b0c8b3fab8f83af37918ef29e7f2e2eb8b5
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 <algorithm>
8 #include <vector>
10 #include "base/base64.h"
11 #include "base/basictypes.h"
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/files/scoped_file.h"
17 #include "base/format_macros.h"
18 #include "base/json/json_reader.h"
19 #include "base/json/json_writer.h"
20 #include "base/logging.h"
21 #include "base/process/kill.h"
22 #include "base/process/launch.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/string_util.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/threading/platform_thread.h"
28 #include "base/time/time.h"
29 #include "base/values.h"
30 #include "chrome/common/chrome_constants.h"
31 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
32 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
33 #include "chrome/test/chromedriver/chrome/chrome_finder.h"
34 #include "chrome/test/chromedriver/chrome/chrome_remote_impl.h"
35 #include "chrome/test/chromedriver/chrome/device_manager.h"
36 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
37 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
38 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
39 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
40 #include "chrome/test/chromedriver/chrome/status.h"
41 #include "chrome/test/chromedriver/chrome/user_data_dir.h"
42 #include "chrome/test/chromedriver/chrome/version.h"
43 #include "chrome/test/chromedriver/chrome/web_view.h"
44 #include "chrome/test/chromedriver/net/port_server.h"
45 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
46 #include "crypto/rsa_private_key.h"
47 #include "crypto/sha2.h"
48 #include "third_party/zlib/google/zip.h"
50 #if defined(OS_POSIX)
51 #include <fcntl.h>
52 #include <sys/stat.h>
53 #include <sys/types.h>
54 #endif
56 namespace {
58 const char* kCommonSwitches[] = {
59 "ignore-certificate-errors", "metrics-recording-only"};
61 #if defined(OS_LINUX)
62 const char* kEnableCrashReport = "enable-crash-reporter-for-testing";
63 #endif
65 Status UnpackAutomationExtension(const base::FilePath& temp_dir,
66 base::FilePath* automation_extension) {
67 std::string decoded_extension;
68 if (!base::Base64Decode(kAutomationExtension, &decoded_extension))
69 return Status(kUnknownError, "failed to base64decode automation extension");
71 base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip");
72 int size = static_cast<int>(decoded_extension.length());
73 if (base::WriteFile(extension_zip, decoded_extension.c_str(), size)
74 != size) {
75 return Status(kUnknownError, "failed to write automation extension zip");
78 base::FilePath extension_dir = temp_dir.AppendASCII("internal");
79 if (!zip::Unzip(extension_zip, extension_dir))
80 return Status(kUnknownError, "failed to unzip automation extension");
82 *automation_extension = extension_dir;
83 return Status(kOk);
86 Status PrepareCommandLine(int port,
87 const Capabilities& capabilities,
88 CommandLine* prepared_command,
89 base::ScopedTempDir* user_data_dir,
90 base::ScopedTempDir* extension_dir,
91 std::vector<std::string>* extension_bg_pages) {
92 base::FilePath program = capabilities.binary;
93 if (program.empty()) {
94 if (!FindChrome(&program))
95 return Status(kUnknownError, "cannot find Chrome binary");
96 } else if (!base::PathExists(program)) {
97 return Status(kUnknownError,
98 base::StringPrintf("no chrome binary at %" PRFilePath,
99 program.value().c_str()));
101 CommandLine command(program);
102 Switches switches;
104 for (size_t i = 0; i < arraysize(kCommonSwitches); ++i)
105 switches.SetSwitch(kCommonSwitches[i]);
106 switches.SetSwitch("disable-hang-monitor");
107 switches.SetSwitch("disable-prompt-on-repost");
108 switches.SetSwitch("disable-sync");
109 switches.SetSwitch("no-first-run");
110 switches.SetSwitch("disable-background-networking");
111 switches.SetSwitch("disable-web-resources");
112 switches.SetSwitch("safebrowsing-disable-auto-update");
113 switches.SetSwitch("safebrowsing-disable-download-protection");
114 switches.SetSwitch("disable-client-side-phishing-detection");
115 switches.SetSwitch("disable-component-update");
116 switches.SetSwitch("disable-default-apps");
117 switches.SetSwitch("enable-logging");
118 switches.SetSwitch("log-level", "0");
119 switches.SetSwitch("password-store", "basic");
120 switches.SetSwitch("use-mock-keychain");
121 switches.SetSwitch("remote-debugging-port", base::IntToString(port));
122 switches.SetSwitch("test-type", "webdriver");
124 for (std::set<std::string>::const_iterator iter =
125 capabilities.exclude_switches.begin();
126 iter != capabilities.exclude_switches.end();
127 ++iter) {
128 switches.RemoveSwitch(*iter);
130 switches.SetFromSwitches(capabilities.switches);
132 if (!switches.HasSwitch("user-data-dir")) {
133 command.AppendArg("data:,");
134 if (!user_data_dir->CreateUniqueTempDir())
135 return Status(kUnknownError, "cannot create temp dir for user data dir");
136 switches.SetSwitch("user-data-dir", user_data_dir->path().value());
137 Status status = internal::PrepareUserDataDir(
138 user_data_dir->path(), capabilities.prefs.get(),
139 capabilities.local_state.get());
140 if (status.IsError())
141 return status;
144 if (!extension_dir->CreateUniqueTempDir()) {
145 return Status(kUnknownError,
146 "cannot create temp dir for unpacking extensions");
148 Status status = internal::ProcessExtensions(capabilities.extensions,
149 extension_dir->path(),
150 true,
151 &switches,
152 extension_bg_pages);
153 if (status.IsError())
154 return status;
155 switches.AppendToCommandLine(&command);
156 *prepared_command = command;
157 return Status(kOk);
160 Status WaitForDevToolsAndCheckVersion(
161 const NetAddress& address,
162 URLRequestContextGetter* context_getter,
163 const SyncWebSocketFactory& socket_factory,
164 const Capabilities* capabilities,
165 scoped_ptr<DevToolsHttpClient>* user_client) {
166 scoped_ptr<DeviceMetrics> device_metrics;
167 if (capabilities && capabilities->device_metrics)
168 device_metrics.reset(new DeviceMetrics(*capabilities->device_metrics));
170 scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient(
171 address, context_getter, socket_factory, device_metrics.Pass()));
172 base::TimeTicks deadline =
173 base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60);
174 Status status = client->Init(deadline - base::TimeTicks::Now());
175 if (status.IsError())
176 return status;
177 if (client->browser_info()->build_no < kMinimumSupportedChromeBuildNo) {
178 return Status(kUnknownError, "Chrome version must be >= " +
179 GetMinimumSupportedChromeVersion());
182 while (base::TimeTicks::Now() < deadline) {
183 WebViewsInfo views_info;
184 client->GetWebViewsInfo(&views_info);
185 for (size_t i = 0; i < views_info.GetSize(); ++i) {
186 if (views_info.Get(i).type == WebViewInfo::kPage) {
187 *user_client = client.Pass();
188 return Status(kOk);
191 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
193 return Status(kUnknownError, "unable to discover open pages");
196 Status CreateBrowserwideDevToolsClientAndConnect(
197 const NetAddress& address,
198 const PerfLoggingPrefs& perf_logging_prefs,
199 const SyncWebSocketFactory& socket_factory,
200 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
201 scoped_ptr<DevToolsClient>* browser_client) {
202 scoped_ptr<DevToolsClient> client(new DevToolsClientImpl(
203 socket_factory, base::StringPrintf("ws://%s/devtools/browser/",
204 address.ToString().c_str()),
205 DevToolsClientImpl::kBrowserwideDevToolsClientId));
206 for (ScopedVector<DevToolsEventListener>::const_iterator it =
207 devtools_event_listeners.begin();
208 it != devtools_event_listeners.end();
209 ++it) {
210 // Only add listeners that subscribe to the browser-wide |DevToolsClient|.
211 // Otherwise, listeners will think this client is associated with a webview,
212 // and will send unrecognized commands to it.
213 if ((*it)->subscribes_to_browser())
214 client->AddListener(*it);
216 // Provide the client regardless of whether it connects, so that Chrome always
217 // has a valid |devtools_websocket_client_|. If not connected, no listeners
218 // will be notified, and client will just return kDisconnected errors if used.
219 *browser_client = client.Pass();
220 // To avoid unnecessary overhead, only connect if tracing is enabled, since
221 // the browser-wide client is currently only used for tracing.
222 if (!perf_logging_prefs.trace_categories.empty()) {
223 Status status = (*browser_client)->ConnectIfNecessary();
224 if (status.IsError())
225 return status;
227 return Status(kOk);
230 Status LaunchRemoteChromeSession(
231 URLRequestContextGetter* context_getter,
232 const SyncWebSocketFactory& socket_factory,
233 const Capabilities& capabilities,
234 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
235 scoped_ptr<Chrome>* chrome) {
236 Status status(kOk);
237 scoped_ptr<DevToolsHttpClient> devtools_http_client;
238 status = WaitForDevToolsAndCheckVersion(
239 capabilities.debugger_address, context_getter, socket_factory,
240 NULL, &devtools_http_client);
241 if (status.IsError()) {
242 return Status(kUnknownError, "cannot connect to chrome at " +
243 capabilities.debugger_address.ToString(),
244 status);
247 scoped_ptr<DevToolsClient> devtools_websocket_client;
248 status = CreateBrowserwideDevToolsClientAndConnect(
249 capabilities.debugger_address, capabilities.perf_logging_prefs,
250 socket_factory, devtools_event_listeners, &devtools_websocket_client);
251 if (status.IsError()) {
252 LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
253 << status.message();
256 chrome->reset(new ChromeRemoteImpl(devtools_http_client.Pass(),
257 devtools_websocket_client.Pass(),
258 devtools_event_listeners));
259 return Status(kOk);
262 Status LaunchDesktopChrome(
263 URLRequestContextGetter* context_getter,
264 int port,
265 scoped_ptr<PortReservation> port_reservation,
266 const SyncWebSocketFactory& socket_factory,
267 const Capabilities& capabilities,
268 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
269 scoped_ptr<Chrome>* chrome) {
270 CommandLine command(CommandLine::NO_PROGRAM);
271 base::ScopedTempDir user_data_dir;
272 base::ScopedTempDir extension_dir;
273 std::vector<std::string> extension_bg_pages;
274 Status status = PrepareCommandLine(port,
275 capabilities,
276 &command,
277 &user_data_dir,
278 &extension_dir,
279 &extension_bg_pages);
280 if (status.IsError())
281 return status;
283 base::LaunchOptions options;
285 #if defined(OS_LINUX)
286 // If minidump path is set in the capability, enable minidump for crashes.
287 if (!capabilities.minidump_path.empty()) {
288 VLOG(0) << "Minidump generation specified. Will save dumps to: "
289 << capabilities.minidump_path;
291 options.environ["CHROME_HEADLESS"] = 1;
292 options.environ["BREAKPAD_DUMP_LOCATION"] = capabilities.minidump_path;
294 if (!command.HasSwitch(kEnableCrashReport))
295 command.AppendSwitch(kEnableCrashReport);
298 // We need to allow new privileges so that chrome's setuid sandbox can run.
299 options.allow_new_privs = true;
300 #endif
302 #if !defined(OS_WIN)
303 if (!capabilities.log_path.empty())
304 options.environ["CHROME_LOG_FILE"] = capabilities.log_path;
305 if (capabilities.detach)
306 options.new_process_group = true;
307 #endif
309 #if defined(OS_POSIX)
310 base::FileHandleMappingVector no_stderr;
311 base::ScopedFD devnull;
312 if (!CommandLine::ForCurrentProcess()->HasSwitch("verbose")) {
313 // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse
314 // users.
315 devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY)));
316 if (!devnull.is_valid())
317 return Status(kUnknownError, "couldn't open /dev/null");
318 no_stderr.push_back(std::make_pair(devnull.get(), STDERR_FILENO));
319 options.fds_to_remap = &no_stderr;
321 #endif
323 #if defined(OS_WIN)
324 std::string command_string = base::WideToUTF8(command.GetCommandLineString());
325 #else
326 std::string command_string = command.GetCommandLineString();
327 #endif
328 VLOG(0) << "Launching chrome: " << command_string;
329 base::ProcessHandle process;
330 if (!base::LaunchProcess(command, options, &process))
331 return Status(kUnknownError, "chrome failed to start");
333 scoped_ptr<DevToolsHttpClient> devtools_http_client;
334 status = WaitForDevToolsAndCheckVersion(
335 NetAddress(port), context_getter, socket_factory, &capabilities,
336 &devtools_http_client);
338 if (status.IsError()) {
339 int exit_code;
340 base::TerminationStatus chrome_status =
341 base::GetTerminationStatus(process, &exit_code);
342 if (chrome_status != base::TERMINATION_STATUS_STILL_RUNNING) {
343 std::string termination_reason;
344 switch (chrome_status) {
345 case base::TERMINATION_STATUS_NORMAL_TERMINATION:
346 termination_reason = "exited normally";
347 break;
348 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
349 termination_reason = "exited abnormally";
350 break;
351 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
352 termination_reason = "was killed";
353 break;
354 case base::TERMINATION_STATUS_PROCESS_CRASHED:
355 termination_reason = "crashed";
356 break;
357 default:
358 termination_reason = "unknown";
359 break;
361 return Status(kUnknownError,
362 "Chrome failed to start: " + termination_reason);
364 if (!base::KillProcess(process, 0, true)) {
365 int exit_code;
366 if (base::GetTerminationStatus(process, &exit_code) ==
367 base::TERMINATION_STATUS_STILL_RUNNING)
368 return Status(kUnknownError, "cannot kill Chrome", status);
370 return status;
373 scoped_ptr<DevToolsClient> devtools_websocket_client;
374 status = CreateBrowserwideDevToolsClientAndConnect(
375 NetAddress(port), capabilities.perf_logging_prefs, socket_factory,
376 devtools_event_listeners, &devtools_websocket_client);
377 if (status.IsError()) {
378 LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
379 << status.message();
382 scoped_ptr<ChromeDesktopImpl> chrome_desktop(
383 new ChromeDesktopImpl(devtools_http_client.Pass(),
384 devtools_websocket_client.Pass(),
385 devtools_event_listeners,
386 port_reservation.Pass(),
387 process,
388 command,
389 &user_data_dir,
390 &extension_dir));
391 for (size_t i = 0; i < extension_bg_pages.size(); ++i) {
392 VLOG(0) << "Waiting for extension bg page load: " << extension_bg_pages[i];
393 scoped_ptr<WebView> web_view;
394 Status status = chrome_desktop->WaitForPageToLoad(
395 extension_bg_pages[i], base::TimeDelta::FromSeconds(10), &web_view);
396 if (status.IsError()) {
397 return Status(kUnknownError,
398 "failed to wait for extension background page to load: " +
399 extension_bg_pages[i],
400 status);
403 *chrome = chrome_desktop.Pass();
404 return Status(kOk);
407 Status LaunchAndroidChrome(
408 URLRequestContextGetter* context_getter,
409 int port,
410 scoped_ptr<PortReservation> port_reservation,
411 const SyncWebSocketFactory& socket_factory,
412 const Capabilities& capabilities,
413 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
414 DeviceManager* device_manager,
415 scoped_ptr<Chrome>* chrome) {
416 Status status(kOk);
417 scoped_ptr<Device> device;
418 if (capabilities.android_device_serial.empty()) {
419 status = device_manager->AcquireDevice(&device);
420 } else {
421 status = device_manager->AcquireSpecificDevice(
422 capabilities.android_device_serial, &device);
424 if (status.IsError())
425 return status;
427 Switches switches(capabilities.switches);
428 for (size_t i = 0; i < arraysize(kCommonSwitches); ++i)
429 switches.SetSwitch(kCommonSwitches[i]);
430 switches.SetSwitch("disable-fre");
431 switches.SetSwitch("enable-remote-debugging");
432 status = device->SetUp(capabilities.android_package,
433 capabilities.android_activity,
434 capabilities.android_process,
435 switches.ToString(),
436 capabilities.android_use_running_app,
437 port);
438 if (status.IsError()) {
439 device->TearDown();
440 return status;
443 scoped_ptr<DevToolsHttpClient> devtools_http_client;
444 status = WaitForDevToolsAndCheckVersion(NetAddress(port),
445 context_getter,
446 socket_factory,
447 &capabilities,
448 &devtools_http_client);
449 if (status.IsError()) {
450 device->TearDown();
451 return status;
454 scoped_ptr<DevToolsClient> devtools_websocket_client;
455 status = CreateBrowserwideDevToolsClientAndConnect(
456 NetAddress(port), capabilities.perf_logging_prefs, socket_factory,
457 devtools_event_listeners, &devtools_websocket_client);
458 if (status.IsError()) {
459 LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
460 << status.message();
463 chrome->reset(new ChromeAndroidImpl(devtools_http_client.Pass(),
464 devtools_websocket_client.Pass(),
465 devtools_event_listeners,
466 port_reservation.Pass(),
467 device.Pass()));
468 return Status(kOk);
471 } // namespace
473 Status LaunchChrome(
474 URLRequestContextGetter* context_getter,
475 const SyncWebSocketFactory& socket_factory,
476 DeviceManager* device_manager,
477 PortServer* port_server,
478 PortManager* port_manager,
479 const Capabilities& capabilities,
480 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
481 scoped_ptr<Chrome>* chrome) {
482 if (capabilities.IsRemoteBrowser()) {
483 return LaunchRemoteChromeSession(
484 context_getter, socket_factory,
485 capabilities, devtools_event_listeners, chrome);
488 int port = 0;
489 scoped_ptr<PortReservation> port_reservation;
490 Status port_status(kOk);
492 if (capabilities.IsAndroid()) {
493 port_status = port_manager->ReservePortFromPool(&port, &port_reservation);
494 if (port_status.IsError())
495 return Status(kUnknownError, "cannot reserve port for Chrome",
496 port_status);
497 return LaunchAndroidChrome(context_getter,
498 port,
499 port_reservation.Pass(),
500 socket_factory,
501 capabilities,
502 devtools_event_listeners,
503 device_manager,
504 chrome);
505 } else {
506 if (port_server)
507 port_status = port_server->ReservePort(&port, &port_reservation);
508 else
509 port_status = port_manager->ReservePort(&port, &port_reservation);
510 if (port_status.IsError())
511 return Status(kUnknownError, "cannot reserve port for Chrome",
512 port_status);
513 return LaunchDesktopChrome(context_getter,
514 port,
515 port_reservation.Pass(),
516 socket_factory,
517 capabilities,
518 devtools_event_listeners,
519 chrome);
523 namespace internal {
525 void ConvertHexadecimalToIDAlphabet(std::string* id) {
526 for (size_t i = 0; i < id->size(); ++i) {
527 int val;
528 if (base::HexStringToInt(base::StringPiece(id->begin() + i,
529 id->begin() + i + 1),
530 &val)) {
531 (*id)[i] = val + 'a';
532 } else {
533 (*id)[i] = 'a';
538 std::string GenerateExtensionId(const std::string& input) {
539 uint8 hash[16];
540 crypto::SHA256HashString(input, hash, sizeof(hash));
541 std::string output =
542 base::StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
543 ConvertHexadecimalToIDAlphabet(&output);
544 return output;
547 Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest,
548 const std::string& id,
549 std::string* bg_page) {
550 std::string bg_page_name;
551 bool persistent = true;
552 manifest->GetBoolean("background.persistent", &persistent);
553 const base::Value* unused_value;
554 if (manifest->Get("background.scripts", &unused_value))
555 bg_page_name = "_generated_background_page.html";
556 manifest->GetString("background.page", &bg_page_name);
557 manifest->GetString("background_page", &bg_page_name);
558 if (bg_page_name.empty() || !persistent)
559 return Status(kOk);
560 *bg_page = "chrome-extension://" + id + "/" + bg_page_name;
561 return Status(kOk);
564 Status ProcessExtension(const std::string& extension,
565 const base::FilePath& temp_dir,
566 base::FilePath* path,
567 std::string* bg_page) {
568 // Decodes extension string.
569 // Some WebDriver client base64 encoders follow RFC 1521, which require that
570 // 'encoded lines be no more than 76 characters long'. Just remove any
571 // newlines.
572 std::string extension_base64;
573 base::RemoveChars(extension, "\n", &extension_base64);
574 std::string decoded_extension;
575 if (!base::Base64Decode(extension_base64, &decoded_extension))
576 return Status(kUnknownError, "cannot base64 decode");
578 // If the file is a crx file, extract the extension's ID from its public key.
579 // Otherwise generate a random public key and use its derived extension ID.
580 std::string public_key;
581 std::string magic_header = decoded_extension.substr(0, 4);
582 if (magic_header.size() != 4)
583 return Status(kUnknownError, "cannot extract magic number");
585 const bool is_crx_file = magic_header == "Cr24";
587 if (is_crx_file) {
588 // Assume a CRX v2 file - see https://developer.chrome.com/extensions/crx.
589 std::string key_len_str = decoded_extension.substr(8, 4);
590 if (key_len_str.size() != 4)
591 return Status(kUnknownError, "cannot extract public key length");
592 uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
593 public_key = decoded_extension.substr(16, key_len);
594 if (key_len != public_key.size())
595 return Status(kUnknownError, "invalid public key length");
596 } else {
597 // Not a CRX file. Generate RSA keypair to get a valid extension id.
598 scoped_ptr<crypto::RSAPrivateKey> key_pair(
599 crypto::RSAPrivateKey::Create(2048));
600 if (!key_pair)
601 return Status(kUnknownError, "cannot generate RSA key pair");
602 std::vector<uint8> public_key_vector;
603 if (!key_pair->ExportPublicKey(&public_key_vector))
604 return Status(kUnknownError, "cannot extract public key");
605 public_key =
606 std::string(reinterpret_cast<char*>(&public_key_vector.front()),
607 public_key_vector.size());
609 std::string public_key_base64;
610 base::Base64Encode(public_key, &public_key_base64);
611 std::string id = GenerateExtensionId(public_key);
613 // Unzip the crx file.
614 base::ScopedTempDir temp_crx_dir;
615 if (!temp_crx_dir.CreateUniqueTempDir())
616 return Status(kUnknownError, "cannot create temp dir");
617 base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
618 int size = static_cast<int>(decoded_extension.length());
619 if (base::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
620 size) {
621 return Status(kUnknownError, "cannot write file");
623 base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id);
624 if (!zip::Unzip(extension_crx, extension_dir))
625 return Status(kUnknownError, "cannot unzip");
627 // Parse the manifest and set the 'key' if not already present.
628 base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json"));
629 std::string manifest_data;
630 if (!base::ReadFileToString(manifest_path, &manifest_data))
631 return Status(kUnknownError, "cannot read manifest");
632 scoped_ptr<base::Value> manifest_value(base::JSONReader::Read(manifest_data));
633 base::DictionaryValue* manifest;
634 if (!manifest_value || !manifest_value->GetAsDictionary(&manifest))
635 return Status(kUnknownError, "invalid manifest");
637 std::string manifest_key_base64;
638 if (manifest->GetString("key", &manifest_key_base64)) {
639 // If there is a key in both the header and the manifest, use the key in the
640 // manifest. This allows chromedriver users users who generate dummy crxs
641 // to set the manifest key and have a consistent ID.
642 std::string manifest_key;
643 if (!base::Base64Decode(manifest_key_base64, &manifest_key))
644 return Status(kUnknownError, "'key' in manifest is not base64 encoded");
645 std::string manifest_id = GenerateExtensionId(manifest_key);
646 if (id != manifest_id) {
647 if (is_crx_file) {
648 LOG(WARNING)
649 << "Public key in crx header is different from key in manifest"
650 << std::endl << "key from header: " << public_key_base64
651 << std::endl << "key from manifest: " << manifest_key_base64
652 << std::endl << "generated extension id from header key: " << id
653 << std::endl << "generated extension id from manifest key: "
654 << manifest_id;
656 id = manifest_id;
658 } else {
659 manifest->SetString("key", public_key_base64);
660 base::JSONWriter::Write(manifest, &manifest_data);
661 if (base::WriteFile(
662 manifest_path, manifest_data.c_str(), manifest_data.size()) !=
663 static_cast<int>(manifest_data.size())) {
664 return Status(kUnknownError, "cannot add 'key' to manifest");
668 // Get extension's background page URL, if there is one.
669 std::string bg_page_tmp;
670 Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp);
671 if (status.IsError())
672 return status;
674 *path = extension_dir;
675 if (bg_page_tmp.size())
676 *bg_page = bg_page_tmp;
677 return Status(kOk);
680 void UpdateExtensionSwitch(Switches* switches,
681 const char name[],
682 const base::FilePath::StringType& extension) {
683 base::FilePath::StringType value = switches->GetSwitchValueNative(name);
684 if (value.length())
685 value += FILE_PATH_LITERAL(",");
686 value += extension;
687 switches->SetSwitch(name, value);
690 Status ProcessExtensions(const std::vector<std::string>& extensions,
691 const base::FilePath& temp_dir,
692 bool include_automation_extension,
693 Switches* switches,
694 std::vector<std::string>* bg_pages) {
695 std::vector<std::string> bg_pages_tmp;
696 std::vector<base::FilePath::StringType> extension_paths;
697 for (size_t i = 0; i < extensions.size(); ++i) {
698 base::FilePath path;
699 std::string bg_page;
700 Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page);
701 if (status.IsError()) {
702 return Status(
703 kUnknownError,
704 base::StringPrintf("cannot process extension #%" PRIuS, i + 1),
705 status);
707 extension_paths.push_back(path.value());
708 if (bg_page.length())
709 bg_pages_tmp.push_back(bg_page);
712 if (include_automation_extension) {
713 base::FilePath automation_extension;
714 Status status = UnpackAutomationExtension(temp_dir, &automation_extension);
715 if (status.IsError())
716 return status;
717 if (switches->HasSwitch("disable-extensions")) {
718 UpdateExtensionSwitch(switches, "load-component-extension",
719 automation_extension.value());
720 } else {
721 extension_paths.push_back(automation_extension.value());
725 if (extension_paths.size()) {
726 base::FilePath::StringType extension_paths_value = JoinString(
727 extension_paths, FILE_PATH_LITERAL(','));
728 UpdateExtensionSwitch(switches, "load-extension", extension_paths_value);
730 bg_pages->swap(bg_pages_tmp);
731 return Status(kOk);
734 Status WritePrefsFile(
735 const std::string& template_string,
736 const base::DictionaryValue* custom_prefs,
737 const base::FilePath& path) {
738 int code;
739 std::string error_msg;
740 scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError(
741 template_string, 0, &code, &error_msg));
742 base::DictionaryValue* prefs;
743 if (!template_value || !template_value->GetAsDictionary(&prefs)) {
744 return Status(kUnknownError,
745 "cannot parse internal JSON template: " + error_msg);
748 if (custom_prefs) {
749 for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd();
750 it.Advance()) {
751 prefs->Set(it.key(), it.value().DeepCopy());
755 std::string prefs_str;
756 base::JSONWriter::Write(prefs, &prefs_str);
757 VLOG(0) << "Populating " << path.BaseName().value()
758 << " file: " << PrettyPrintValue(*prefs);
759 if (static_cast<int>(prefs_str.length()) != base::WriteFile(
760 path, prefs_str.c_str(), prefs_str.length())) {
761 return Status(kUnknownError, "failed to write prefs file");
763 return Status(kOk);
766 Status PrepareUserDataDir(
767 const base::FilePath& user_data_dir,
768 const base::DictionaryValue* custom_prefs,
769 const base::DictionaryValue* custom_local_state) {
770 base::FilePath default_dir =
771 user_data_dir.AppendASCII(chrome::kInitialProfile);
772 if (!base::CreateDirectory(default_dir))
773 return Status(kUnknownError, "cannot create default profile directory");
775 Status status =
776 WritePrefsFile(kPreferences,
777 custom_prefs,
778 default_dir.Append(chrome::kPreferencesFilename));
779 if (status.IsError())
780 return status;
782 status = WritePrefsFile(kLocalState,
783 custom_local_state,
784 user_data_dir.Append(chrome::kLocalStateFilename));
785 if (status.IsError())
786 return status;
788 // Write empty "First Run" file, otherwise Chrome will wipe the default
789 // profile that was written.
790 if (base::WriteFile(
791 user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) {
792 return Status(kUnknownError, "failed to write first run file");
794 return Status(kOk);
797 } // namespace internal