Enable PPAPI tests for non-SFI mode.
[chromium-blink-merge.git] / chrome / test / chromedriver / chrome_launcher.cc
blob24da0bcd81f4c46263ceeb41e8ccec0b960f95bc
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/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"
46 #if defined(OS_POSIX)
47 #include <fcntl.h>
48 #include <sys/stat.h>
49 #include <sys/types.h>
50 #endif
52 namespace {
54 const char* kCommonSwitches[] = {
55 "ignore-certificate-errors", "metrics-recording-only"};
57 #if defined(OS_LINUX)
58 const char* kEnableCrashReport = "enable-crash-reporter-for-testing";
59 #endif
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)
70 != 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;
79 return Status(kOk);
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);
98 Switches switches;
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();
122 ++iter) {
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())
136 return status;
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(),
145 true,
146 &switches,
147 extension_bg_pages);
148 if (status.IsError())
149 return status;
150 switches.AppendToCommandLine(&command);
151 *prepared_command = command;
152 return Status(kOk);
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())
166 return status;
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();
178 return Status(kOk);
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) {
192 Status status(kOk);
193 scoped_ptr<DevToolsHttpClient> devtools_client;
194 status = WaitForDevToolsAndCheckVersion(
195 capabilities.debugger_address, context_getter, socket_factory,
196 &devtools_client);
197 if (status.IsError()) {
198 return Status(kUnknownError, "cannot connect to chrome at " +
199 capabilities.debugger_address.ToString(),
200 status);
202 chrome->reset(new ChromeExistingImpl(devtools_client.Pass(),
203 devtools_event_listeners));
204 return Status(kOk);
207 Status LaunchDesktopChrome(
208 URLRequestContextGetter* context_getter,
209 int port,
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,
220 capabilities,
221 &command,
222 &user_data_dir,
223 &extension_dir,
224 &extension_bg_pages);
225 if (status.IsError())
226 return status;
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;
245 #endif
247 #if !defined(OS_WIN)
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;
252 #endif
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
259 // users.
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;
266 #endif
268 #if defined(OS_WIN)
269 std::string command_string = base::WideToUTF8(command.GetCommandLineString());
270 #else
271 std::string command_string = command.GetCommandLineString();
272 #endif
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()) {
283 int exit_code;
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";
291 break;
292 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
293 termination_reason = "exited abnormally";
294 break;
295 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
296 termination_reason = "was killed";
297 break;
298 case base::TERMINATION_STATUS_PROCESS_CRASHED:
299 termination_reason = "crashed";
300 break;
301 default:
302 termination_reason = "unknown";
303 break;
305 return Status(kUnknownError,
306 "Chrome failed to start: " + termination_reason);
308 if (!base::KillProcess(process, 0, true)) {
309 int exit_code;
310 if (base::GetTerminationStatus(process, &exit_code) ==
311 base::TERMINATION_STATUS_STILL_RUNNING)
312 return Status(kUnknownError, "cannot kill Chrome", status);
314 return status;
316 scoped_ptr<ChromeDesktopImpl> chrome_desktop(
317 new ChromeDesktopImpl(devtools_client.Pass(),
318 devtools_event_listeners,
319 port_reservation.Pass(),
320 process,
321 command,
322 &user_data_dir,
323 &extension_dir));
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],
333 status);
336 *chrome = chrome_desktop.Pass();
337 return Status(kOk);
340 Status LaunchAndroidChrome(
341 URLRequestContextGetter* context_getter,
342 int port,
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) {
349 Status status(kOk);
350 scoped_ptr<Device> device;
351 if (capabilities.android_device_serial.empty()) {
352 status = device_manager->AcquireDevice(&device);
353 } else {
354 status = device_manager->AcquireSpecificDevice(
355 capabilities.android_device_serial, &device);
357 if (status.IsError())
358 return status;
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,
368 switches.ToString(),
369 capabilities.android_use_running_app,
370 port);
371 if (status.IsError()) {
372 device->TearDown();
373 return status;
376 scoped_ptr<DevToolsHttpClient> devtools_client;
377 status = WaitForDevToolsAndCheckVersion(NetAddress(port),
378 context_getter,
379 socket_factory,
380 &devtools_client);
381 if (status.IsError()) {
382 device->TearDown();
383 return status;
386 chrome->reset(new ChromeAndroidImpl(devtools_client.Pass(),
387 devtools_event_listeners,
388 port_reservation.Pass(),
389 device.Pass()));
390 return Status(kOk);
393 } // namespace
395 Status LaunchChrome(
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);
410 int port = 0;
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",
418 port_status);
419 return LaunchAndroidChrome(context_getter,
420 port,
421 port_reservation.Pass(),
422 socket_factory,
423 capabilities,
424 devtools_event_listeners,
425 device_manager,
426 chrome);
427 } else {
428 if (port_server)
429 port_status = port_server->ReservePort(&port, &port_reservation);
430 else
431 port_status = port_manager->ReservePort(&port, &port_reservation);
432 if (port_status.IsError())
433 return Status(kUnknownError, "cannot reserve port for Chrome",
434 port_status);
435 return LaunchDesktopChrome(context_getter,
436 port,
437 port_reservation.Pass(),
438 socket_factory,
439 capabilities,
440 devtools_event_listeners,
441 chrome);
445 namespace internal {
447 void ConvertHexadecimalToIDAlphabet(std::string* id) {
448 for (size_t i = 0; i < id->size(); ++i) {
449 int val;
450 if (base::HexStringToInt(base::StringPiece(id->begin() + i,
451 id->begin() + i + 1),
452 &val)) {
453 (*id)[i] = val + 'a';
454 } else {
455 (*id)[i] = 'a';
460 std::string GenerateExtensionId(const std::string& input) {
461 uint8 hash[16];
462 crypto::SHA256HashString(input, hash, sizeof(hash));
463 std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
464 ConvertHexadecimalToIDAlphabet(&output);
465 return 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)
480 return Status(kOk);
481 *bg_page = "chrome-extension://" + id + "/" + bg_page_name;
482 return Status(kOk);
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
492 // newlines.
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) !=
519 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) {
546 LOG(WARNING)
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: "
552 << manifest_id;
553 id = manifest_id;
555 } else {
556 manifest->SetString("key", public_key_base64);
557 base::JSONWriter::Write(manifest, &manifest_data);
558 if (base::WriteFile(
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())
569 return status;
571 *path = extension_dir;
572 if (bg_page_tmp.size())
573 *bg_page = bg_page_tmp;
574 return Status(kOk);
577 void UpdateExtensionSwitch(Switches* switches,
578 const char name[],
579 const base::FilePath::StringType& extension) {
580 base::FilePath::StringType value = switches->GetSwitchValueNative(name);
581 if (value.length())
582 value += FILE_PATH_LITERAL(",");
583 value += extension;
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,
590 Switches* switches,
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) {
595 base::FilePath path;
596 std::string bg_page;
597 Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page);
598 if (status.IsError()) {
599 return Status(
600 kUnknownError,
601 base::StringPrintf("cannot process extension #%" PRIuS, i + 1),
602 status);
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())
613 return status;
614 if (switches->HasSwitch("disable-extensions")) {
615 UpdateExtensionSwitch(switches, "load-component-extension",
616 automation_extension.value());
617 } else {
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);
628 return Status(kOk);
631 Status WritePrefsFile(
632 const std::string& template_string,
633 const base::DictionaryValue* custom_prefs,
634 const base::FilePath& path) {
635 int code;
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);
645 if (custom_prefs) {
646 for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd();
647 it.Advance()) {
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");
660 return Status(kOk);
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");
672 Status status =
673 WritePrefsFile(kPreferences,
674 custom_prefs,
675 default_dir.Append(chrome::kPreferencesFilename));
676 if (status.IsError())
677 return status;
679 status = WritePrefsFile(kLocalState,
680 custom_local_state,
681 user_data_dir.Append(chrome::kLocalStateFilename));
682 if (status.IsError())
683 return status;
685 // Write empty "First Run" file, otherwise Chrome will wipe the default
686 // profile that was written.
687 if (base::WriteFile(
688 user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) {
689 return Status(kUnknownError, "failed to write first run file");
691 return Status(kOk);
694 } // namespace internal