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/command_line.h"
13 #include "base/file_util.h"
14 #include "base/files/file_path.h"
15 #include "base/files/scoped_file.h"
16 #include "base/format_macros.h"
17 #include "base/json/json_reader.h"
18 #include "base/json/json_writer.h"
19 #include "base/logging.h"
20 #include "base/process/kill.h"
21 #include "base/process/launch.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "base/threading/platform_thread.h"
27 #include "base/time/time.h"
28 #include "base/values.h"
29 #include "chrome/common/chrome_constants.h"
30 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
31 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
32 #include "chrome/test/chromedriver/chrome/chrome_existing_impl.h"
33 #include "chrome/test/chromedriver/chrome/chrome_finder.h"
34 #include "chrome/test/chromedriver/chrome/device_manager.h"
35 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
36 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
37 #include "chrome/test/chromedriver/chrome/status.h"
38 #include "chrome/test/chromedriver/chrome/user_data_dir.h"
39 #include "chrome/test/chromedriver/chrome/version.h"
40 #include "chrome/test/chromedriver/chrome/web_view.h"
41 #include "chrome/test/chromedriver/net/port_server.h"
42 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
43 #include "crypto/sha2.h"
44 #include "third_party/zlib/google/zip.h"
49 #include <sys/types.h>
54 const char* kCommonSwitches
[] = {
55 "ignore-certificate-errors", "metrics-recording-only"};
58 const char* kEnableCrashReport
= "enable-crash-reporter-for-testing";
61 Status
UnpackAutomationExtension(const base::FilePath
& temp_dir
,
62 base::FilePath
* automation_extension
) {
63 std::string decoded_extension
;
64 if (!base::Base64Decode(kAutomationExtension
, &decoded_extension
))
65 return Status(kUnknownError
, "failed to base64decode automation extension");
67 base::FilePath extension_zip
= temp_dir
.AppendASCII("internal.zip");
68 int size
= static_cast<int>(decoded_extension
.length());
69 if (base::WriteFile(extension_zip
, decoded_extension
.c_str(), size
)
71 return Status(kUnknownError
, "failed to write automation extension zip");
74 base::FilePath extension_dir
= temp_dir
.AppendASCII("internal");
75 if (!zip::Unzip(extension_zip
, extension_dir
))
76 return Status(kUnknownError
, "failed to unzip automation extension");
78 *automation_extension
= extension_dir
;
82 Status
PrepareCommandLine(int port
,
83 const Capabilities
& capabilities
,
84 CommandLine
* prepared_command
,
85 base::ScopedTempDir
* user_data_dir
,
86 base::ScopedTempDir
* extension_dir
,
87 std::vector
<std::string
>* extension_bg_pages
) {
88 base::FilePath program
= capabilities
.binary
;
89 if (program
.empty()) {
90 if (!FindChrome(&program
))
91 return Status(kUnknownError
, "cannot find Chrome binary");
92 } else if (!base::PathExists(program
)) {
93 return Status(kUnknownError
,
94 base::StringPrintf("no chrome binary at %" PRFilePath
,
95 program
.value().c_str()));
97 CommandLine
command(program
);
100 for (size_t i
= 0; i
< arraysize(kCommonSwitches
); ++i
)
101 switches
.SetSwitch(kCommonSwitches
[i
]);
102 switches
.SetSwitch("disable-hang-monitor");
103 switches
.SetSwitch("disable-prompt-on-repost");
104 switches
.SetSwitch("disable-sync");
105 switches
.SetSwitch("no-first-run");
106 switches
.SetSwitch("disable-background-networking");
107 switches
.SetSwitch("disable-web-resources");
108 switches
.SetSwitch("safebrowsing-disable-auto-update");
109 switches
.SetSwitch("safebrowsing-disable-download-protection");
110 switches
.SetSwitch("disable-client-side-phishing-detection");
111 switches
.SetSwitch("disable-component-update");
112 switches
.SetSwitch("disable-default-apps");
113 switches
.SetSwitch("enable-logging");
114 switches
.SetSwitch("logging-level", "1");
115 switches
.SetSwitch("password-store", "basic");
116 switches
.SetSwitch("use-mock-keychain");
117 switches
.SetSwitch("remote-debugging-port", base::IntToString(port
));
119 for (std::set
<std::string
>::const_iterator iter
=
120 capabilities
.exclude_switches
.begin();
121 iter
!= capabilities
.exclude_switches
.end();
123 switches
.RemoveSwitch(*iter
);
125 switches
.SetFromSwitches(capabilities
.switches
);
127 if (!switches
.HasSwitch("user-data-dir")) {
128 command
.AppendArg("data:,");
129 if (!user_data_dir
->CreateUniqueTempDir())
130 return Status(kUnknownError
, "cannot create temp dir for user data dir");
131 switches
.SetSwitch("user-data-dir", user_data_dir
->path().value());
132 Status status
= internal::PrepareUserDataDir(
133 user_data_dir
->path(), capabilities
.prefs
.get(),
134 capabilities
.local_state
.get());
135 if (status
.IsError())
139 if (!extension_dir
->CreateUniqueTempDir()) {
140 return Status(kUnknownError
,
141 "cannot create temp dir for unpacking extensions");
143 Status status
= internal::ProcessExtensions(capabilities
.extensions
,
144 extension_dir
->path(),
148 if (status
.IsError())
150 switches
.AppendToCommandLine(&command
);
151 *prepared_command
= command
;
155 Status
WaitForDevToolsAndCheckVersion(
156 const NetAddress
& address
,
157 URLRequestContextGetter
* context_getter
,
158 const SyncWebSocketFactory
& socket_factory
,
159 scoped_ptr
<DevToolsHttpClient
>* user_client
) {
160 scoped_ptr
<DevToolsHttpClient
> client(new DevToolsHttpClient(
161 address
, context_getter
, socket_factory
));
162 base::TimeTicks deadline
=
163 base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60);
164 Status status
= client
->Init(deadline
- base::TimeTicks::Now());
165 if (status
.IsError())
167 if (client
->build_no() < kMinimumSupportedChromeBuildNo
) {
168 return Status(kUnknownError
, "Chrome version must be >= " +
169 GetMinimumSupportedChromeVersion());
172 while (base::TimeTicks::Now() < deadline
) {
173 WebViewsInfo views_info
;
174 client
->GetWebViewsInfo(&views_info
);
175 for (size_t i
= 0; i
< views_info
.GetSize(); ++i
) {
176 if (views_info
.Get(i
).type
== WebViewInfo::kPage
) {
177 *user_client
= client
.Pass();
181 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
183 return Status(kUnknownError
, "unable to discover open pages");
186 Status
LaunchExistingChromeSession(
187 URLRequestContextGetter
* context_getter
,
188 const SyncWebSocketFactory
& socket_factory
,
189 const Capabilities
& capabilities
,
190 ScopedVector
<DevToolsEventListener
>& devtools_event_listeners
,
191 scoped_ptr
<Chrome
>* chrome
) {
193 scoped_ptr
<DevToolsHttpClient
> devtools_client
;
194 status
= WaitForDevToolsAndCheckVersion(
195 capabilities
.debugger_address
, context_getter
, socket_factory
,
197 if (status
.IsError()) {
198 return Status(kUnknownError
, "cannot connect to chrome at " +
199 capabilities
.debugger_address
.ToString(),
202 chrome
->reset(new ChromeExistingImpl(devtools_client
.Pass(),
203 devtools_event_listeners
));
207 Status
LaunchDesktopChrome(
208 URLRequestContextGetter
* context_getter
,
210 scoped_ptr
<PortReservation
> port_reservation
,
211 const SyncWebSocketFactory
& socket_factory
,
212 const Capabilities
& capabilities
,
213 ScopedVector
<DevToolsEventListener
>& devtools_event_listeners
,
214 scoped_ptr
<Chrome
>* chrome
) {
215 CommandLine
command(CommandLine::NO_PROGRAM
);
216 base::ScopedTempDir user_data_dir
;
217 base::ScopedTempDir extension_dir
;
218 std::vector
<std::string
> extension_bg_pages
;
219 Status status
= PrepareCommandLine(port
,
224 &extension_bg_pages
);
225 if (status
.IsError())
228 base::LaunchOptions options
;
230 #if defined(OS_LINUX)
231 // If minidump path is set in the capability, enable minidump for crashes.
232 if (!capabilities
.minidump_path
.empty()) {
233 VLOG(0) << "Minidump generation specified. Will save dumps to: "
234 << capabilities
.minidump_path
;
236 options
.environ
["CHROME_HEADLESS"] = 1;
237 options
.environ
["BREAKPAD_DUMP_LOCATION"] = capabilities
.minidump_path
;
239 if (!command
.HasSwitch(kEnableCrashReport
))
240 command
.AppendSwitch(kEnableCrashReport
);
243 // We need to allow new privileges so that chrome's setuid sandbox can run.
244 options
.allow_new_privs
= true;
248 if (!capabilities
.log_path
.empty())
249 options
.environ
["CHROME_LOG_FILE"] = capabilities
.log_path
;
250 if (capabilities
.detach
)
251 options
.new_process_group
= true;
254 #if defined(OS_POSIX)
255 base::FileHandleMappingVector no_stderr
;
256 base::ScopedFD devnull
;
257 if (!CommandLine::ForCurrentProcess()->HasSwitch("verbose")) {
258 // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse
260 devnull
.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY
)));
261 if (!devnull
.is_valid())
262 return Status(kUnknownError
, "couldn't open /dev/null");
263 no_stderr
.push_back(std::make_pair(devnull
.get(), STDERR_FILENO
));
264 options
.fds_to_remap
= &no_stderr
;
269 std::string command_string
= base::WideToUTF8(command
.GetCommandLineString());
271 std::string command_string
= command
.GetCommandLineString();
273 VLOG(0) << "Launching chrome: " << command_string
;
274 base::ProcessHandle process
;
275 if (!base::LaunchProcess(command
, options
, &process
))
276 return Status(kUnknownError
, "chrome failed to start");
278 scoped_ptr
<DevToolsHttpClient
> devtools_client
;
279 status
= WaitForDevToolsAndCheckVersion(
280 NetAddress(port
), context_getter
, socket_factory
, &devtools_client
);
282 if (status
.IsError()) {
284 base::TerminationStatus chrome_status
=
285 base::GetTerminationStatus(process
, &exit_code
);
286 if (chrome_status
!= base::TERMINATION_STATUS_STILL_RUNNING
) {
287 std::string termination_reason
;
288 switch (chrome_status
) {
289 case base::TERMINATION_STATUS_NORMAL_TERMINATION
:
290 termination_reason
= "exited normally";
292 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION
:
293 termination_reason
= "exited abnormally";
295 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED
:
296 termination_reason
= "was killed";
298 case base::TERMINATION_STATUS_PROCESS_CRASHED
:
299 termination_reason
= "crashed";
302 termination_reason
= "unknown";
305 return Status(kUnknownError
,
306 "Chrome failed to start: " + termination_reason
);
308 if (!base::KillProcess(process
, 0, true)) {
310 if (base::GetTerminationStatus(process
, &exit_code
) ==
311 base::TERMINATION_STATUS_STILL_RUNNING
)
312 return Status(kUnknownError
, "cannot kill Chrome", status
);
316 scoped_ptr
<ChromeDesktopImpl
> chrome_desktop(
317 new ChromeDesktopImpl(devtools_client
.Pass(),
318 devtools_event_listeners
,
319 port_reservation
.Pass(),
324 for (size_t i
= 0; i
< extension_bg_pages
.size(); ++i
) {
325 VLOG(0) << "Waiting for extension bg page load: " << extension_bg_pages
[i
];
326 scoped_ptr
<WebView
> web_view
;
327 Status status
= chrome_desktop
->WaitForPageToLoad(
328 extension_bg_pages
[i
], base::TimeDelta::FromSeconds(10), &web_view
);
329 if (status
.IsError()) {
330 return Status(kUnknownError
,
331 "failed to wait for extension background page to load: " +
332 extension_bg_pages
[i
],
336 *chrome
= chrome_desktop
.Pass();
340 Status
LaunchAndroidChrome(
341 URLRequestContextGetter
* context_getter
,
343 scoped_ptr
<PortReservation
> port_reservation
,
344 const SyncWebSocketFactory
& socket_factory
,
345 const Capabilities
& capabilities
,
346 ScopedVector
<DevToolsEventListener
>& devtools_event_listeners
,
347 DeviceManager
* device_manager
,
348 scoped_ptr
<Chrome
>* chrome
) {
350 scoped_ptr
<Device
> device
;
351 if (capabilities
.android_device_serial
.empty()) {
352 status
= device_manager
->AcquireDevice(&device
);
354 status
= device_manager
->AcquireSpecificDevice(
355 capabilities
.android_device_serial
, &device
);
357 if (status
.IsError())
360 Switches
switches(capabilities
.switches
);
361 for (size_t i
= 0; i
< arraysize(kCommonSwitches
); ++i
)
362 switches
.SetSwitch(kCommonSwitches
[i
]);
363 switches
.SetSwitch("disable-fre");
364 switches
.SetSwitch("enable-remote-debugging");
365 status
= device
->SetUp(capabilities
.android_package
,
366 capabilities
.android_activity
,
367 capabilities
.android_process
,
369 capabilities
.android_use_running_app
,
371 if (status
.IsError()) {
376 scoped_ptr
<DevToolsHttpClient
> devtools_client
;
377 status
= WaitForDevToolsAndCheckVersion(NetAddress(port
),
381 if (status
.IsError()) {
386 chrome
->reset(new ChromeAndroidImpl(devtools_client
.Pass(),
387 devtools_event_listeners
,
388 port_reservation
.Pass(),
396 URLRequestContextGetter
* context_getter
,
397 const SyncWebSocketFactory
& socket_factory
,
398 DeviceManager
* device_manager
,
399 PortServer
* port_server
,
400 PortManager
* port_manager
,
401 const Capabilities
& capabilities
,
402 ScopedVector
<DevToolsEventListener
>& devtools_event_listeners
,
403 scoped_ptr
<Chrome
>* chrome
) {
404 if (capabilities
.IsExistingBrowser()) {
405 return LaunchExistingChromeSession(
406 context_getter
, socket_factory
,
407 capabilities
, devtools_event_listeners
, chrome
);
411 scoped_ptr
<PortReservation
> port_reservation
;
412 Status
port_status(kOk
);
414 if (capabilities
.IsAndroid()) {
415 port_status
= port_manager
->ReservePortFromPool(&port
, &port_reservation
);
416 if (port_status
.IsError())
417 return Status(kUnknownError
, "cannot reserve port for Chrome",
419 return LaunchAndroidChrome(context_getter
,
421 port_reservation
.Pass(),
424 devtools_event_listeners
,
429 port_status
= port_server
->ReservePort(&port
, &port_reservation
);
431 port_status
= port_manager
->ReservePort(&port
, &port_reservation
);
432 if (port_status
.IsError())
433 return Status(kUnknownError
, "cannot reserve port for Chrome",
435 return LaunchDesktopChrome(context_getter
,
437 port_reservation
.Pass(),
440 devtools_event_listeners
,
447 void ConvertHexadecimalToIDAlphabet(std::string
* id
) {
448 for (size_t i
= 0; i
< id
->size(); ++i
) {
450 if (base::HexStringToInt(base::StringPiece(id
->begin() + i
,
451 id
->begin() + i
+ 1),
453 (*id
)[i
] = val
+ 'a';
460 std::string
GenerateExtensionId(const std::string
& input
) {
462 crypto::SHA256HashString(input
, hash
, sizeof(hash
));
463 std::string output
= StringToLowerASCII(base::HexEncode(hash
, sizeof(hash
)));
464 ConvertHexadecimalToIDAlphabet(&output
);
468 Status
GetExtensionBackgroundPage(const base::DictionaryValue
* manifest
,
469 const std::string
& id
,
470 std::string
* bg_page
) {
471 std::string bg_page_name
;
472 bool persistent
= true;
473 manifest
->GetBoolean("background.persistent", &persistent
);
474 const base::Value
* unused_value
;
475 if (manifest
->Get("background.scripts", &unused_value
))
476 bg_page_name
= "_generated_background_page.html";
477 manifest
->GetString("background.page", &bg_page_name
);
478 manifest
->GetString("background_page", &bg_page_name
);
479 if (bg_page_name
.empty() || !persistent
)
481 *bg_page
= "chrome-extension://" + id
+ "/" + bg_page_name
;
485 Status
ProcessExtension(const std::string
& extension
,
486 const base::FilePath
& temp_dir
,
487 base::FilePath
* path
,
488 std::string
* bg_page
) {
489 // Decodes extension string.
490 // Some WebDriver client base64 encoders follow RFC 1521, which require that
491 // 'encoded lines be no more than 76 characters long'. Just remove any
493 std::string extension_base64
;
494 base::RemoveChars(extension
, "\n", &extension_base64
);
495 std::string decoded_extension
;
496 if (!base::Base64Decode(extension_base64
, &decoded_extension
))
497 return Status(kUnknownError
, "cannot base64 decode");
499 // Get extension's ID from public key in crx file.
500 // Assumes crx v2. See http://developer.chrome.com/extensions/crx.html.
501 std::string key_len_str
= decoded_extension
.substr(8, 4);
502 if (key_len_str
.size() != 4)
503 return Status(kUnknownError
, "cannot extract public key length");
504 uint32 key_len
= *reinterpret_cast<const uint32
*>(key_len_str
.c_str());
505 std::string public_key
= decoded_extension
.substr(16, key_len
);
506 if (key_len
!= public_key
.size())
507 return Status(kUnknownError
, "invalid public key length");
508 std::string public_key_base64
;
509 base::Base64Encode(public_key
, &public_key_base64
);
510 std::string id
= GenerateExtensionId(public_key
);
512 // Unzip the crx file.
513 base::ScopedTempDir temp_crx_dir
;
514 if (!temp_crx_dir
.CreateUniqueTempDir())
515 return Status(kUnknownError
, "cannot create temp dir");
516 base::FilePath extension_crx
= temp_crx_dir
.path().AppendASCII("temp.crx");
517 int size
= static_cast<int>(decoded_extension
.length());
518 if (base::WriteFile(extension_crx
, decoded_extension
.c_str(), size
) !=
520 return Status(kUnknownError
, "cannot write file");
522 base::FilePath extension_dir
= temp_dir
.AppendASCII("extension_" + id
);
523 if (!zip::Unzip(extension_crx
, extension_dir
))
524 return Status(kUnknownError
, "cannot unzip");
526 // Parse the manifest and set the 'key' if not already present.
527 base::FilePath
manifest_path(extension_dir
.AppendASCII("manifest.json"));
528 std::string manifest_data
;
529 if (!base::ReadFileToString(manifest_path
, &manifest_data
))
530 return Status(kUnknownError
, "cannot read manifest");
531 scoped_ptr
<base::Value
> manifest_value(base::JSONReader::Read(manifest_data
));
532 base::DictionaryValue
* manifest
;
533 if (!manifest_value
|| !manifest_value
->GetAsDictionary(&manifest
))
534 return Status(kUnknownError
, "invalid manifest");
536 std::string manifest_key_base64
;
537 if (manifest
->GetString("key", &manifest_key_base64
)) {
538 // If there is a key in both the header and the manifest, use the key in the
539 // manifest. This allows chromedriver users users who generate dummy crxs
540 // to set the manifest key and have a consistent ID.
541 std::string manifest_key
;
542 if (!base::Base64Decode(manifest_key_base64
, &manifest_key
))
543 return Status(kUnknownError
, "'key' in manifest is not base64 encoded");
544 std::string manifest_id
= GenerateExtensionId(manifest_key
);
545 if (id
!= manifest_id
) {
547 << "Public key in crx header is different from key in manifest"
548 << std::endl
<< "key from header: " << public_key_base64
549 << std::endl
<< "key from manifest: " << manifest_key_base64
550 << std::endl
<< "generated extension id from header key: " << id
551 << std::endl
<< "generated extension id from manifest key: "
556 manifest
->SetString("key", public_key_base64
);
557 base::JSONWriter::Write(manifest
, &manifest_data
);
559 manifest_path
, manifest_data
.c_str(), manifest_data
.size()) !=
560 static_cast<int>(manifest_data
.size())) {
561 return Status(kUnknownError
, "cannot add 'key' to manifest");
565 // Get extension's background page URL, if there is one.
566 std::string bg_page_tmp
;
567 Status status
= GetExtensionBackgroundPage(manifest
, id
, &bg_page_tmp
);
568 if (status
.IsError())
571 *path
= extension_dir
;
572 if (bg_page_tmp
.size())
573 *bg_page
= bg_page_tmp
;
577 void UpdateExtensionSwitch(Switches
* switches
,
579 const base::FilePath::StringType
& extension
) {
580 base::FilePath::StringType value
= switches
->GetSwitchValueNative(name
);
582 value
+= FILE_PATH_LITERAL(",");
584 switches
->SetSwitch(name
, value
);
587 Status
ProcessExtensions(const std::vector
<std::string
>& extensions
,
588 const base::FilePath
& temp_dir
,
589 bool include_automation_extension
,
591 std::vector
<std::string
>* bg_pages
) {
592 std::vector
<std::string
> bg_pages_tmp
;
593 std::vector
<base::FilePath::StringType
> extension_paths
;
594 for (size_t i
= 0; i
< extensions
.size(); ++i
) {
597 Status status
= ProcessExtension(extensions
[i
], temp_dir
, &path
, &bg_page
);
598 if (status
.IsError()) {
601 base::StringPrintf("cannot process extension #%" PRIuS
, i
+ 1),
604 extension_paths
.push_back(path
.value());
605 if (bg_page
.length())
606 bg_pages_tmp
.push_back(bg_page
);
609 if (include_automation_extension
) {
610 base::FilePath automation_extension
;
611 Status status
= UnpackAutomationExtension(temp_dir
, &automation_extension
);
612 if (status
.IsError())
614 if (switches
->HasSwitch("disable-extensions")) {
615 UpdateExtensionSwitch(switches
, "load-component-extension",
616 automation_extension
.value());
618 extension_paths
.push_back(automation_extension
.value());
622 if (extension_paths
.size()) {
623 base::FilePath::StringType extension_paths_value
= JoinString(
624 extension_paths
, FILE_PATH_LITERAL(','));
625 UpdateExtensionSwitch(switches
, "load-extension", extension_paths_value
);
627 bg_pages
->swap(bg_pages_tmp
);
631 Status
WritePrefsFile(
632 const std::string
& template_string
,
633 const base::DictionaryValue
* custom_prefs
,
634 const base::FilePath
& path
) {
636 std::string error_msg
;
637 scoped_ptr
<base::Value
> template_value(base::JSONReader::ReadAndReturnError(
638 template_string
, 0, &code
, &error_msg
));
639 base::DictionaryValue
* prefs
;
640 if (!template_value
|| !template_value
->GetAsDictionary(&prefs
)) {
641 return Status(kUnknownError
,
642 "cannot parse internal JSON template: " + error_msg
);
646 for (base::DictionaryValue::Iterator
it(*custom_prefs
); !it
.IsAtEnd();
648 prefs
->Set(it
.key(), it
.value().DeepCopy());
652 std::string prefs_str
;
653 base::JSONWriter::Write(prefs
, &prefs_str
);
654 VLOG(0) << "Populating " << path
.BaseName().value()
655 << " file: " << PrettyPrintValue(*prefs
);
656 if (static_cast<int>(prefs_str
.length()) != base::WriteFile(
657 path
, prefs_str
.c_str(), prefs_str
.length())) {
658 return Status(kUnknownError
, "failed to write prefs file");
663 Status
PrepareUserDataDir(
664 const base::FilePath
& user_data_dir
,
665 const base::DictionaryValue
* custom_prefs
,
666 const base::DictionaryValue
* custom_local_state
) {
667 base::FilePath default_dir
=
668 user_data_dir
.AppendASCII(chrome::kInitialProfile
);
669 if (!base::CreateDirectory(default_dir
))
670 return Status(kUnknownError
, "cannot create default profile directory");
673 WritePrefsFile(kPreferences
,
675 default_dir
.Append(chrome::kPreferencesFilename
));
676 if (status
.IsError())
679 status
= WritePrefsFile(kLocalState
,
681 user_data_dir
.Append(chrome::kLocalStateFilename
));
682 if (status
.IsError())
685 // Write empty "First Run" file, otherwise Chrome will wipe the default
686 // profile that was written.
688 user_data_dir
.Append(chrome::kFirstRunSentinel
), "", 0) != 0) {
689 return Status(kUnknownError
, "failed to write first run file");
694 } // namespace internal