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"
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/process/process.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/stringprintf.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/threading/platform_thread.h"
29 #include "base/time/time.h"
30 #include "base/values.h"
31 #include "chrome/common/chrome_constants.h"
32 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
33 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
34 #include "chrome/test/chromedriver/chrome/chrome_finder.h"
35 #include "chrome/test/chromedriver/chrome/chrome_remote_impl.h"
36 #include "chrome/test/chromedriver/chrome/device_manager.h"
37 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
38 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
39 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
40 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
41 #include "chrome/test/chromedriver/chrome/status.h"
42 #include "chrome/test/chromedriver/chrome/user_data_dir.h"
43 #include "chrome/test/chromedriver/chrome/version.h"
44 #include "chrome/test/chromedriver/chrome/web_view.h"
45 #include "chrome/test/chromedriver/net/port_server.h"
46 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
47 #include "crypto/rsa_private_key.h"
48 #include "crypto/sha2.h"
49 #include "third_party/zlib/google/zip.h"
54 #include <sys/types.h>
56 #include "chrome/test/chromedriver/keycode_text_conversion.h"
61 const char* const kCommonSwitches
[] = {
62 "disable-popup-blocking",
63 "ignore-certificate-errors",
64 "metrics-recording-only"
67 const char* const kDesktopSwitches
[] = {
68 "disable-hang-monitor",
69 "disable-prompt-on-repost",
72 "disable-background-networking",
73 "disable-web-resources",
74 "safebrowsing-disable-auto-update",
75 "safebrowsing-disable-download-protection",
76 "disable-client-side-phishing-detection",
77 "disable-component-update",
78 "disable-default-apps",
81 "password-store=basic",
86 const char* const kAndroidSwitches
[] = {
88 "enable-remote-debugging"
92 const char kEnableCrashReport
[] = "enable-crash-reporter-for-testing";
95 Status
UnpackAutomationExtension(const base::FilePath
& temp_dir
,
96 base::FilePath
* automation_extension
) {
97 std::string decoded_extension
;
98 if (!base::Base64Decode(kAutomationExtension
, &decoded_extension
))
99 return Status(kUnknownError
, "failed to base64decode automation extension");
101 base::FilePath extension_zip
= temp_dir
.AppendASCII("internal.zip");
102 int size
= static_cast<int>(decoded_extension
.length());
103 if (base::WriteFile(extension_zip
, decoded_extension
.c_str(), size
)
105 return Status(kUnknownError
, "failed to write automation extension zip");
108 base::FilePath extension_dir
= temp_dir
.AppendASCII("internal");
109 if (!zip::Unzip(extension_zip
, extension_dir
))
110 return Status(kUnknownError
, "failed to unzip automation extension");
112 *automation_extension
= extension_dir
;
116 Status
PrepareCommandLine(uint16 port
,
117 const Capabilities
& capabilities
,
118 base::CommandLine
* prepared_command
,
119 base::ScopedTempDir
* user_data_dir
,
120 base::ScopedTempDir
* extension_dir
,
121 std::vector
<std::string
>* extension_bg_pages
) {
122 base::FilePath program
= capabilities
.binary
;
123 if (program
.empty()) {
124 if (!FindChrome(&program
))
125 return Status(kUnknownError
, "cannot find Chrome binary");
126 } else if (!base::PathExists(program
)) {
127 return Status(kUnknownError
,
128 base::StringPrintf("no chrome binary at %" PRFilePath
,
129 program
.value().c_str()));
131 base::CommandLine
command(program
);
134 for (const auto& common_switch
: kCommonSwitches
)
135 switches
.SetUnparsedSwitch(common_switch
);
136 for (const auto& desktop_switch
: kDesktopSwitches
)
137 switches
.SetUnparsedSwitch(desktop_switch
);
138 switches
.SetSwitch("remote-debugging-port", base::IntToString(port
));
139 for (const auto& excluded_switch
: capabilities
.exclude_switches
) {
140 switches
.RemoveSwitch(excluded_switch
);
142 switches
.SetFromSwitches(capabilities
.switches
);
144 base::FilePath user_data_dir_path
;
145 if (switches
.HasSwitch("user-data-dir")) {
146 user_data_dir_path
= base::FilePath(
147 switches
.GetSwitchValueNative("user-data-dir"));
149 command
.AppendArg("data:,");
150 if (!user_data_dir
->CreateUniqueTempDir())
151 return Status(kUnknownError
, "cannot create temp dir for user data dir");
152 switches
.SetSwitch("user-data-dir", user_data_dir
->path().value());
153 user_data_dir_path
= user_data_dir
->path();
156 Status status
= internal::PrepareUserDataDir(user_data_dir_path
,
157 capabilities
.prefs
.get(),
158 capabilities
.local_state
.get());
159 if (status
.IsError())
162 if (!extension_dir
->CreateUniqueTempDir()) {
163 return Status(kUnknownError
,
164 "cannot create temp dir for unpacking extensions");
166 status
= internal::ProcessExtensions(capabilities
.extensions
,
167 extension_dir
->path(),
171 if (status
.IsError())
173 switches
.AppendToCommandLine(&command
);
174 *prepared_command
= command
;
178 Status
WaitForDevToolsAndCheckVersion(
179 const NetAddress
& address
,
180 URLRequestContextGetter
* context_getter
,
181 const SyncWebSocketFactory
& socket_factory
,
182 const Capabilities
* capabilities
,
183 scoped_ptr
<DevToolsHttpClient
>* user_client
) {
184 scoped_ptr
<DeviceMetrics
> device_metrics
;
185 if (capabilities
&& capabilities
->device_metrics
)
186 device_metrics
.reset(new DeviceMetrics(*capabilities
->device_metrics
));
188 scoped_ptr
<DevToolsHttpClient
> client(new DevToolsHttpClient(
189 address
, context_getter
, socket_factory
, device_metrics
.Pass()));
190 base::TimeTicks deadline
=
191 base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60);
192 Status status
= client
->Init(deadline
- base::TimeTicks::Now());
193 if (status
.IsError())
196 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
197 if (cmd_line
->HasSwitch("disable-build-check")) {
198 LOG(WARNING
) << "You are using an unsupported command-line switch: "
199 "--disable-build-check. Please don't report bugs that "
200 "cannot be reproduced with this switch removed.";
201 } else if (client
->browser_info()->build_no
<
202 kMinimumSupportedChromeBuildNo
) {
203 return Status(kUnknownError
, "Chrome version must be >= " +
204 GetMinimumSupportedChromeVersion());
207 while (base::TimeTicks::Now() < deadline
) {
208 WebViewsInfo views_info
;
209 client
->GetWebViewsInfo(&views_info
);
210 for (size_t i
= 0; i
< views_info
.GetSize(); ++i
) {
211 if (views_info
.Get(i
).type
== WebViewInfo::kPage
) {
212 *user_client
= client
.Pass();
216 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
218 return Status(kUnknownError
, "unable to discover open pages");
221 Status
CreateBrowserwideDevToolsClientAndConnect(
222 const NetAddress
& address
,
223 const PerfLoggingPrefs
& perf_logging_prefs
,
224 const SyncWebSocketFactory
& socket_factory
,
225 const ScopedVector
<DevToolsEventListener
>& devtools_event_listeners
,
226 scoped_ptr
<DevToolsClient
>* browser_client
) {
227 scoped_ptr
<DevToolsClient
> client(new DevToolsClientImpl(
228 socket_factory
, base::StringPrintf("ws://%s/devtools/browser/",
229 address
.ToString().c_str()),
230 DevToolsClientImpl::kBrowserwideDevToolsClientId
));
231 for (ScopedVector
<DevToolsEventListener
>::const_iterator it
=
232 devtools_event_listeners
.begin();
233 it
!= devtools_event_listeners
.end();
235 // Only add listeners that subscribe to the browser-wide |DevToolsClient|.
236 // Otherwise, listeners will think this client is associated with a webview,
237 // and will send unrecognized commands to it.
238 if ((*it
)->subscribes_to_browser())
239 client
->AddListener(*it
);
241 // Provide the client regardless of whether it connects, so that Chrome always
242 // has a valid |devtools_websocket_client_|. If not connected, no listeners
243 // will be notified, and client will just return kDisconnected errors if used.
244 *browser_client
= client
.Pass();
245 // To avoid unnecessary overhead, only connect if tracing is enabled, since
246 // the browser-wide client is currently only used for tracing.
247 if (!perf_logging_prefs
.trace_categories
.empty()) {
248 Status status
= (*browser_client
)->ConnectIfNecessary();
249 if (status
.IsError())
255 Status
LaunchRemoteChromeSession(
256 URLRequestContextGetter
* context_getter
,
257 const SyncWebSocketFactory
& socket_factory
,
258 const Capabilities
& capabilities
,
259 ScopedVector
<DevToolsEventListener
>* devtools_event_listeners
,
260 scoped_ptr
<Chrome
>* chrome
) {
262 scoped_ptr
<DevToolsHttpClient
> devtools_http_client
;
263 status
= WaitForDevToolsAndCheckVersion(
264 capabilities
.debugger_address
, context_getter
, socket_factory
,
265 NULL
, &devtools_http_client
);
266 if (status
.IsError()) {
267 return Status(kUnknownError
, "cannot connect to chrome at " +
268 capabilities
.debugger_address
.ToString(),
272 scoped_ptr
<DevToolsClient
> devtools_websocket_client
;
273 status
= CreateBrowserwideDevToolsClientAndConnect(
274 capabilities
.debugger_address
, capabilities
.perf_logging_prefs
,
275 socket_factory
, *devtools_event_listeners
, &devtools_websocket_client
);
276 if (status
.IsError()) {
277 LOG(WARNING
) << "Browser-wide DevTools client failed to connect: "
281 chrome
->reset(new ChromeRemoteImpl(devtools_http_client
.Pass(),
282 devtools_websocket_client
.Pass(),
283 *devtools_event_listeners
));
287 Status
LaunchDesktopChrome(
288 URLRequestContextGetter
* context_getter
,
290 scoped_ptr
<PortReservation
> port_reservation
,
291 const SyncWebSocketFactory
& socket_factory
,
292 const Capabilities
& capabilities
,
293 ScopedVector
<DevToolsEventListener
>* devtools_event_listeners
,
294 scoped_ptr
<Chrome
>* chrome
) {
295 base::CommandLine
command(base::CommandLine::NO_PROGRAM
);
296 base::ScopedTempDir user_data_dir
;
297 base::ScopedTempDir extension_dir
;
298 std::vector
<std::string
> extension_bg_pages
;
299 Status status
= PrepareCommandLine(port
,
304 &extension_bg_pages
);
305 if (status
.IsError())
308 base::LaunchOptions options
;
310 #if defined(OS_LINUX)
311 // If minidump path is set in the capability, enable minidump for crashes.
312 if (!capabilities
.minidump_path
.empty()) {
313 VLOG(0) << "Minidump generation specified. Will save dumps to: "
314 << capabilities
.minidump_path
;
316 options
.environ
["CHROME_HEADLESS"] = 1;
317 options
.environ
["BREAKPAD_DUMP_LOCATION"] = capabilities
.minidump_path
;
319 if (!command
.HasSwitch(kEnableCrashReport
))
320 command
.AppendSwitch(kEnableCrashReport
);
323 // We need to allow new privileges so that chrome's setuid sandbox can run.
324 options
.allow_new_privs
= true;
328 if (!capabilities
.log_path
.empty())
329 options
.environ
["CHROME_LOG_FILE"] = capabilities
.log_path
;
330 if (capabilities
.detach
)
331 options
.new_process_group
= true;
334 #if defined(OS_POSIX)
335 base::FileHandleMappingVector no_stderr
;
336 base::ScopedFD devnull
;
337 if (!base::CommandLine::ForCurrentProcess()->HasSwitch("verbose")) {
338 // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse
340 devnull
.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY
)));
341 if (!devnull
.is_valid())
342 return Status(kUnknownError
, "couldn't open /dev/null");
343 no_stderr
.push_back(std::make_pair(devnull
.get(), STDERR_FILENO
));
344 options
.fds_to_remap
= &no_stderr
;
346 #elif defined(OS_WIN)
347 if (!SwitchToUSKeyboardLayout())
348 VLOG(0) << "Can not set to US keyboard layout - Some keycodes may be"
349 "interpreted incorrectly";
353 std::string command_string
= base::WideToUTF8(command
.GetCommandLineString());
355 std::string command_string
= command
.GetCommandLineString();
357 VLOG(0) << "Launching chrome: " << command_string
;
358 base::Process process
= base::LaunchProcess(command
, options
);
359 if (!process
.IsValid())
360 return Status(kUnknownError
, "chrome failed to start");
362 scoped_ptr
<DevToolsHttpClient
> devtools_http_client
;
363 status
= WaitForDevToolsAndCheckVersion(
364 NetAddress(port
), context_getter
, socket_factory
, &capabilities
,
365 &devtools_http_client
);
367 if (status
.IsError()) {
369 base::TerminationStatus chrome_status
=
370 base::GetTerminationStatus(process
.Handle(), &exit_code
);
371 if (chrome_status
!= base::TERMINATION_STATUS_STILL_RUNNING
) {
372 std::string termination_reason
;
373 switch (chrome_status
) {
374 case base::TERMINATION_STATUS_NORMAL_TERMINATION
:
375 termination_reason
= "exited normally";
377 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION
:
378 termination_reason
= "exited abnormally";
380 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED
:
381 #if defined(OS_CHROMEOS)
382 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM
:
384 termination_reason
= "was killed";
386 case base::TERMINATION_STATUS_PROCESS_CRASHED
:
387 termination_reason
= "crashed";
389 case base::TERMINATION_STATUS_LAUNCH_FAILED
:
390 termination_reason
= "failed to launch";
393 termination_reason
= "unknown";
396 return Status(kUnknownError
,
397 "Chrome failed to start: " + termination_reason
);
399 if (!process
.Terminate(0, true)) {
401 if (base::GetTerminationStatus(process
.Handle(), &exit_code
) ==
402 base::TERMINATION_STATUS_STILL_RUNNING
)
403 return Status(kUnknownError
, "cannot kill Chrome", status
);
408 scoped_ptr
<DevToolsClient
> devtools_websocket_client
;
409 status
= CreateBrowserwideDevToolsClientAndConnect(
410 NetAddress(port
), capabilities
.perf_logging_prefs
, socket_factory
,
411 *devtools_event_listeners
, &devtools_websocket_client
);
412 if (status
.IsError()) {
413 LOG(WARNING
) << "Browser-wide DevTools client failed to connect: "
417 scoped_ptr
<ChromeDesktopImpl
> chrome_desktop(
418 new ChromeDesktopImpl(devtools_http_client
.Pass(),
419 devtools_websocket_client
.Pass(),
420 *devtools_event_listeners
,
421 port_reservation
.Pass(),
426 for (size_t i
= 0; i
< extension_bg_pages
.size(); ++i
) {
427 VLOG(0) << "Waiting for extension bg page load: " << extension_bg_pages
[i
];
428 scoped_ptr
<WebView
> web_view
;
429 Status status
= chrome_desktop
->WaitForPageToLoad(
430 extension_bg_pages
[i
], base::TimeDelta::FromSeconds(10), &web_view
);
431 if (status
.IsError()) {
432 return Status(kUnknownError
,
433 "failed to wait for extension background page to load: " +
434 extension_bg_pages
[i
],
438 *chrome
= chrome_desktop
.Pass();
442 Status
LaunchAndroidChrome(
443 URLRequestContextGetter
* context_getter
,
445 scoped_ptr
<PortReservation
> port_reservation
,
446 const SyncWebSocketFactory
& socket_factory
,
447 const Capabilities
& capabilities
,
448 ScopedVector
<DevToolsEventListener
>* devtools_event_listeners
,
449 DeviceManager
* device_manager
,
450 scoped_ptr
<Chrome
>* chrome
) {
452 scoped_ptr
<Device
> device
;
453 if (capabilities
.android_device_serial
.empty()) {
454 status
= device_manager
->AcquireDevice(&device
);
456 status
= device_manager
->AcquireSpecificDevice(
457 capabilities
.android_device_serial
, &device
);
459 if (status
.IsError())
462 Switches
switches(capabilities
.switches
);
463 for (auto common_switch
: kCommonSwitches
)
464 switches
.SetUnparsedSwitch(common_switch
);
465 for (auto android_switch
: kAndroidSwitches
)
466 switches
.SetUnparsedSwitch(android_switch
);
467 status
= device
->SetUp(capabilities
.android_package
,
468 capabilities
.android_activity
,
469 capabilities
.android_process
,
471 capabilities
.android_use_running_app
,
473 if (status
.IsError()) {
478 scoped_ptr
<DevToolsHttpClient
> devtools_http_client
;
479 status
= WaitForDevToolsAndCheckVersion(NetAddress(port
),
483 &devtools_http_client
);
484 if (status
.IsError()) {
489 scoped_ptr
<DevToolsClient
> devtools_websocket_client
;
490 status
= CreateBrowserwideDevToolsClientAndConnect(
491 NetAddress(port
), capabilities
.perf_logging_prefs
, socket_factory
,
492 *devtools_event_listeners
, &devtools_websocket_client
);
493 if (status
.IsError()) {
494 LOG(WARNING
) << "Browser-wide DevTools client failed to connect: "
498 chrome
->reset(new ChromeAndroidImpl(devtools_http_client
.Pass(),
499 devtools_websocket_client
.Pass(),
500 *devtools_event_listeners
,
501 port_reservation
.Pass(),
509 URLRequestContextGetter
* context_getter
,
510 const SyncWebSocketFactory
& socket_factory
,
511 DeviceManager
* device_manager
,
512 PortServer
* port_server
,
513 PortManager
* port_manager
,
514 const Capabilities
& capabilities
,
515 ScopedVector
<DevToolsEventListener
>* devtools_event_listeners
,
516 scoped_ptr
<Chrome
>* chrome
) {
517 if (capabilities
.IsRemoteBrowser()) {
518 return LaunchRemoteChromeSession(
519 context_getter
, socket_factory
,
520 capabilities
, devtools_event_listeners
, chrome
);
524 scoped_ptr
<PortReservation
> port_reservation
;
525 Status
port_status(kOk
);
527 if (capabilities
.IsAndroid()) {
529 port_status
= port_server
->ReservePort(&port
, &port_reservation
);
531 port_status
= port_manager
->ReservePortFromPool(&port
, &port_reservation
);
532 if (port_status
.IsError())
533 return Status(kUnknownError
, "cannot reserve port for Chrome",
535 return LaunchAndroidChrome(context_getter
,
537 port_reservation
.Pass(),
540 devtools_event_listeners
,
545 port_status
= port_server
->ReservePort(&port
, &port_reservation
);
547 port_status
= port_manager
->ReservePort(&port
, &port_reservation
);
548 if (port_status
.IsError())
549 return Status(kUnknownError
, "cannot reserve port for Chrome",
551 return LaunchDesktopChrome(context_getter
,
553 port_reservation
.Pass(),
556 devtools_event_listeners
,
563 void ConvertHexadecimalToIDAlphabet(std::string
* id
) {
564 for (size_t i
= 0; i
< id
->size(); ++i
) {
566 if (base::HexStringToInt(base::StringPiece(id
->begin() + i
,
567 id
->begin() + i
+ 1),
569 (*id
)[i
] = val
+ 'a';
576 std::string
GenerateExtensionId(const std::string
& input
) {
578 crypto::SHA256HashString(input
, hash
, sizeof(hash
));
579 std::string output
= base::ToLowerASCII(base::HexEncode(hash
, sizeof(hash
)));
580 ConvertHexadecimalToIDAlphabet(&output
);
584 Status
GetExtensionBackgroundPage(const base::DictionaryValue
* manifest
,
585 const std::string
& id
,
586 std::string
* bg_page
) {
587 std::string bg_page_name
;
588 bool persistent
= true;
589 manifest
->GetBoolean("background.persistent", &persistent
);
590 const base::Value
* unused_value
;
591 if (manifest
->Get("background.scripts", &unused_value
))
592 bg_page_name
= "_generated_background_page.html";
593 manifest
->GetString("background.page", &bg_page_name
);
594 manifest
->GetString("background_page", &bg_page_name
);
595 if (bg_page_name
.empty() || !persistent
)
597 *bg_page
= "chrome-extension://" + id
+ "/" + bg_page_name
;
601 Status
ProcessExtension(const std::string
& extension
,
602 const base::FilePath
& temp_dir
,
603 base::FilePath
* path
,
604 std::string
* bg_page
) {
605 // Decodes extension string.
606 // Some WebDriver client base64 encoders follow RFC 1521, which require that
607 // 'encoded lines be no more than 76 characters long'. Just remove any
609 std::string extension_base64
;
610 base::RemoveChars(extension
, "\n", &extension_base64
);
611 std::string decoded_extension
;
612 if (!base::Base64Decode(extension_base64
, &decoded_extension
))
613 return Status(kUnknownError
, "cannot base64 decode");
615 // If the file is a crx file, extract the extension's ID from its public key.
616 // Otherwise generate a random public key and use its derived extension ID.
617 std::string public_key
;
618 std::string magic_header
= decoded_extension
.substr(0, 4);
619 if (magic_header
.size() != 4)
620 return Status(kUnknownError
, "cannot extract magic number");
622 const bool is_crx_file
= magic_header
== "Cr24";
625 // Assume a CRX v2 file - see https://developer.chrome.com/extensions/crx.
626 std::string key_len_str
= decoded_extension
.substr(8, 4);
627 if (key_len_str
.size() != 4)
628 return Status(kUnknownError
, "cannot extract public key length");
629 uint32 key_len
= *reinterpret_cast<const uint32
*>(key_len_str
.c_str());
630 public_key
= decoded_extension
.substr(16, key_len
);
631 if (key_len
!= public_key
.size())
632 return Status(kUnknownError
, "invalid public key length");
634 // Not a CRX file. Generate RSA keypair to get a valid extension id.
635 scoped_ptr
<crypto::RSAPrivateKey
> key_pair(
636 crypto::RSAPrivateKey::Create(2048));
638 return Status(kUnknownError
, "cannot generate RSA key pair");
639 std::vector
<uint8
> public_key_vector
;
640 if (!key_pair
->ExportPublicKey(&public_key_vector
))
641 return Status(kUnknownError
, "cannot extract public key");
643 std::string(reinterpret_cast<char*>(&public_key_vector
.front()),
644 public_key_vector
.size());
646 std::string public_key_base64
;
647 base::Base64Encode(public_key
, &public_key_base64
);
648 std::string id
= GenerateExtensionId(public_key
);
650 // Unzip the crx file.
651 base::ScopedTempDir temp_crx_dir
;
652 if (!temp_crx_dir
.CreateUniqueTempDir())
653 return Status(kUnknownError
, "cannot create temp dir");
654 base::FilePath extension_crx
= temp_crx_dir
.path().AppendASCII("temp.crx");
655 int size
= static_cast<int>(decoded_extension
.length());
656 if (base::WriteFile(extension_crx
, decoded_extension
.c_str(), size
) !=
658 return Status(kUnknownError
, "cannot write file");
660 base::FilePath extension_dir
= temp_dir
.AppendASCII("extension_" + id
);
661 if (!zip::Unzip(extension_crx
, extension_dir
))
662 return Status(kUnknownError
, "cannot unzip");
664 // Parse the manifest and set the 'key' if not already present.
665 base::FilePath
manifest_path(extension_dir
.AppendASCII("manifest.json"));
666 std::string manifest_data
;
667 if (!base::ReadFileToString(manifest_path
, &manifest_data
))
668 return Status(kUnknownError
, "cannot read manifest");
669 scoped_ptr
<base::Value
> manifest_value
=
670 base::JSONReader::Read(manifest_data
);
671 base::DictionaryValue
* manifest
;
672 if (!manifest_value
|| !manifest_value
->GetAsDictionary(&manifest
))
673 return Status(kUnknownError
, "invalid manifest");
675 std::string manifest_key_base64
;
676 if (manifest
->GetString("key", &manifest_key_base64
)) {
677 // If there is a key in both the header and the manifest, use the key in the
678 // manifest. This allows chromedriver users users who generate dummy crxs
679 // to set the manifest key and have a consistent ID.
680 std::string manifest_key
;
681 if (!base::Base64Decode(manifest_key_base64
, &manifest_key
))
682 return Status(kUnknownError
, "'key' in manifest is not base64 encoded");
683 std::string manifest_id
= GenerateExtensionId(manifest_key
);
684 if (id
!= manifest_id
) {
687 << "Public key in crx header is different from key in manifest"
688 << std::endl
<< "key from header: " << public_key_base64
689 << std::endl
<< "key from manifest: " << manifest_key_base64
690 << std::endl
<< "generated extension id from header key: " << id
691 << std::endl
<< "generated extension id from manifest key: "
697 manifest
->SetString("key", public_key_base64
);
698 base::JSONWriter::Write(*manifest
, &manifest_data
);
700 manifest_path
, manifest_data
.c_str(), manifest_data
.size()) !=
701 static_cast<int>(manifest_data
.size())) {
702 return Status(kUnknownError
, "cannot add 'key' to manifest");
706 // Get extension's background page URL, if there is one.
707 std::string bg_page_tmp
;
708 Status status
= GetExtensionBackgroundPage(manifest
, id
, &bg_page_tmp
);
709 if (status
.IsError())
712 *path
= extension_dir
;
713 if (bg_page_tmp
.size())
714 *bg_page
= bg_page_tmp
;
718 void UpdateExtensionSwitch(Switches
* switches
,
720 const base::FilePath::StringType
& extension
) {
721 base::FilePath::StringType value
= switches
->GetSwitchValueNative(name
);
723 value
+= FILE_PATH_LITERAL(",");
725 switches
->SetSwitch(name
, value
);
728 Status
ProcessExtensions(const std::vector
<std::string
>& extensions
,
729 const base::FilePath
& temp_dir
,
730 bool include_automation_extension
,
732 std::vector
<std::string
>* bg_pages
) {
733 std::vector
<std::string
> bg_pages_tmp
;
734 std::vector
<base::FilePath::StringType
> extension_paths
;
735 for (size_t i
= 0; i
< extensions
.size(); ++i
) {
738 Status status
= ProcessExtension(extensions
[i
], temp_dir
, &path
, &bg_page
);
739 if (status
.IsError()) {
742 base::StringPrintf("cannot process extension #%" PRIuS
, i
+ 1),
745 extension_paths
.push_back(path
.value());
746 if (bg_page
.length())
747 bg_pages_tmp
.push_back(bg_page
);
750 if (include_automation_extension
) {
751 base::FilePath automation_extension
;
752 Status status
= UnpackAutomationExtension(temp_dir
, &automation_extension
);
753 if (status
.IsError())
755 if (switches
->HasSwitch("disable-extensions")) {
756 UpdateExtensionSwitch(switches
, "load-component-extension",
757 automation_extension
.value());
759 extension_paths
.push_back(automation_extension
.value());
763 if (extension_paths
.size()) {
764 base::FilePath::StringType extension_paths_value
= base::JoinString(
765 extension_paths
, base::FilePath::StringType(1, ','));
766 UpdateExtensionSwitch(switches
, "load-extension", extension_paths_value
);
768 bg_pages
->swap(bg_pages_tmp
);
772 Status
WritePrefsFile(
773 const std::string
& template_string
,
774 const base::DictionaryValue
* custom_prefs
,
775 const base::FilePath
& path
) {
777 std::string error_msg
;
778 scoped_ptr
<base::Value
> template_value(
779 base::JSONReader::DeprecatedReadAndReturnError(template_string
, 0, &code
,
781 base::DictionaryValue
* prefs
;
782 if (!template_value
|| !template_value
->GetAsDictionary(&prefs
)) {
783 return Status(kUnknownError
,
784 "cannot parse internal JSON template: " + error_msg
);
788 for (base::DictionaryValue::Iterator
it(*custom_prefs
); !it
.IsAtEnd();
790 prefs
->Set(it
.key(), it
.value().DeepCopy());
794 std::string prefs_str
;
795 base::JSONWriter::Write(*prefs
, &prefs_str
);
796 VLOG(0) << "Populating " << path
.BaseName().value()
797 << " file: " << PrettyPrintValue(*prefs
);
798 if (static_cast<int>(prefs_str
.length()) != base::WriteFile(
799 path
, prefs_str
.c_str(), prefs_str
.length())) {
800 return Status(kUnknownError
, "failed to write prefs file");
805 Status
PrepareUserDataDir(
806 const base::FilePath
& user_data_dir
,
807 const base::DictionaryValue
* custom_prefs
,
808 const base::DictionaryValue
* custom_local_state
) {
809 base::FilePath default_dir
=
810 user_data_dir
.AppendASCII(chrome::kInitialProfile
);
811 if (!base::CreateDirectory(default_dir
))
812 return Status(kUnknownError
, "cannot create default profile directory");
814 std::string preferences
;
815 base::FilePath preferences_path
=
816 default_dir
.Append(chrome::kPreferencesFilename
);
818 if (base::PathExists(preferences_path
))
819 base::ReadFileToString(preferences_path
, &preferences
);
821 preferences
= kPreferences
;
824 WritePrefsFile(preferences
,
826 default_dir
.Append(chrome::kPreferencesFilename
));
827 if (status
.IsError())
830 std::string local_state
;
831 base::FilePath local_state_path
=
832 user_data_dir
.Append(chrome::kLocalStateFilename
);
834 if (base::PathExists(local_state_path
))
835 base::ReadFileToString(local_state_path
, &local_state
);
837 local_state
= kLocalState
;
839 status
= WritePrefsFile(local_state
,
841 user_data_dir
.Append(chrome::kLocalStateFilename
));
842 if (status
.IsError())
845 // Write empty "First Run" file, otherwise Chrome will wipe the default
846 // profile that was written.
848 user_data_dir
.Append(chrome::kFirstRunSentinel
), "", 0) != 0) {
849 return Status(kUnknownError
, "failed to write first run file");
854 } // namespace internal