1 // Copyright (c) 2012 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 "cloud_print/service/win/chrome_launcher.h"
7 #include "base/base_switches.h"
8 #include "base/command_line.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/process/process.h"
14 #include "base/process/process.h"
15 #include "base/values.h"
16 #include "base/win/registry.h"
17 #include "base/win/scoped_handle.h"
18 #include "base/win/scoped_process_information.h"
19 #include "chrome/common/chrome_constants.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/common/pref_names.h"
22 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
23 #include "cloud_print/common/win/cloud_print_utils.h"
24 #include "cloud_print/service/service_constants.h"
25 #include "cloud_print/service/win/service_utils.h"
26 #include "components/cloud_devices/common/cloud_devices_urls.h"
27 #include "google_apis/gaia/gaia_urls.h"
28 #include "net/base/url_util.h"
33 const int kShutdownTimeoutMs
= 30 * 1000;
34 const int kUsageUpdateTimeoutMs
= 6 * 3600 * 1000; // 6 hours.
36 static const base::char16 kAutoRunKeyPath
[] =
37 L
"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
39 // Terminates any process.
40 void ShutdownChrome(base::Process process
, DWORD thread_id
) {
41 if (::PostThreadMessage(thread_id
, WM_QUIT
, 0, 0) &&
42 WAIT_OBJECT_0
== ::WaitForSingleObject(process
.Handle(),
43 kShutdownTimeoutMs
)) {
46 LOG(ERROR
) << "Failed to shutdown process.";
47 process
.Terminate(0, true);
50 BOOL CALLBACK
CloseIfPidEqual(HWND wnd
, LPARAM lparam
) {
52 ::GetWindowThreadProcessId(wnd
, &pid
);
53 if (pid
== static_cast<DWORD
>(lparam
))
54 ::PostMessage(wnd
, WM_CLOSE
, 0, 0);
58 void CloseAllProcessWindows(HANDLE process
) {
59 ::EnumWindows(&CloseIfPidEqual
, GetProcessId(process
));
62 // Close Chrome browser window.
63 void CloseChrome(base::Process process
, DWORD thread_id
) {
64 CloseAllProcessWindows(process
.Handle());
66 ::WaitForSingleObject(process
.Handle(), kShutdownTimeoutMs
)) {
69 ShutdownChrome(process
.Pass(), thread_id
);
72 bool LaunchProcess(const base::CommandLine
& cmdline
,
73 base::win::ScopedHandle
* process_handle
,
75 STARTUPINFO startup_info
= {};
76 startup_info
.cb
= sizeof(startup_info
);
77 startup_info
.dwFlags
= STARTF_USESHOWWINDOW
;
78 startup_info
.wShowWindow
= SW_SHOW
;
80 PROCESS_INFORMATION temp_process_info
= {};
81 base::FilePath::StringType
writable_cmdline_str(
82 cmdline
.GetCommandLineString());
83 if (!CreateProcess(NULL
,
84 &writable_cmdline_str
[0], NULL
, NULL
,
85 FALSE
, 0, NULL
, NULL
, &startup_info
, &temp_process_info
)) {
88 base::win::ScopedProcessInformation
process_info(temp_process_info
);
91 process_handle
->Set(process_info
.TakeProcessHandle());
94 *thread_id
= process_info
.thread_id();
99 std::string
ReadAndUpdateServiceState(const base::FilePath
& directory
,
100 const std::string
& proxy_id
) {
102 base::FilePath file_path
= directory
.Append(chrome::kServiceStateFileName
);
103 if (!base::ReadFileToString(file_path
, &json
)) {
104 return std::string();
107 scoped_ptr
<base::Value
> service_state(base::JSONReader::Read(json
));
108 base::DictionaryValue
* dictionary
= NULL
;
109 if (!service_state
->GetAsDictionary(&dictionary
) || !dictionary
) {
110 return std::string();
113 bool enabled
= false;
114 if (!dictionary
->GetBoolean(prefs::kCloudPrintProxyEnabled
, &enabled
) ||
116 return std::string();
119 std::string refresh_token
;
120 if (!dictionary
->GetString(prefs::kCloudPrintRobotRefreshToken
,
122 refresh_token
.empty()) {
123 return std::string();
126 // Remove everything except kCloudPrintRoot.
127 scoped_ptr
<base::Value
> cloud_print_root
;
128 dictionary
->Remove(prefs::kCloudPrintRoot
, &cloud_print_root
);
130 dictionary
->Set(prefs::kCloudPrintRoot
, cloud_print_root
.release());
132 dictionary
->SetBoolean(prefs::kCloudPrintXmppPingEnabled
, true);
133 if (!proxy_id
.empty()) // Reuse proxy id if we already had one.
134 dictionary
->SetString(prefs::kCloudPrintProxyId
, proxy_id
);
136 base::JSONWriter::WriteWithOptions(*dictionary
,
137 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
142 void DeleteAutorunKeys(const base::FilePath
& user_data_dir
) {
143 base::win::RegKey
key(HKEY_CURRENT_USER
, kAutoRunKeyPath
, KEY_SET_VALUE
);
146 std::vector
<base::string16
> to_delete
;
148 base::FilePath abs_user_data_dir
= base::MakeAbsoluteFilePath(user_data_dir
);
151 base::win::RegistryValueIterator
value(HKEY_CURRENT_USER
, kAutoRunKeyPath
);
152 for (; value
.Valid(); ++value
) {
153 if (value
.Type() == REG_SZ
&& value
.Value()) {
154 base::CommandLine cmd
= base::CommandLine::FromString(value
.Value());
155 if (cmd
.GetSwitchValueASCII(switches::kProcessType
) ==
156 switches::kServiceProcess
&&
157 cmd
.HasSwitch(switches::kUserDataDir
)) {
158 base::FilePath path_from_reg
= base::MakeAbsoluteFilePath(
159 cmd
.GetSwitchValuePath(switches::kUserDataDir
));
160 if (path_from_reg
== abs_user_data_dir
) {
161 to_delete
.push_back(value
.Name());
168 for (size_t i
= 0; i
< to_delete
.size(); ++i
) {
169 key
.DeleteValue(to_delete
[i
].c_str());
175 ChromeLauncher::ChromeLauncher(const base::FilePath
& user_data
)
176 : user_data_(user_data
), stop_event_(true, true) {
179 ChromeLauncher::~ChromeLauncher() {
182 bool ChromeLauncher::Start() {
183 DeleteAutorunKeys(user_data_
);
185 thread_
.reset(new base::DelegateSimpleThread(this, "chrome_launcher"));
190 void ChromeLauncher::Stop() {
191 stop_event_
.Signal();
196 void ChromeLauncher::Run() {
197 const base::TimeDelta default_time_out
= base::TimeDelta::FromSeconds(1);
198 const base::TimeDelta max_time_out
= base::TimeDelta::FromHours(1);
200 for (base::TimeDelta time_out
= default_time_out
;;
201 time_out
= std::min(time_out
* 2, max_time_out
)) {
202 base::FilePath chrome_path
=
203 chrome_launcher_support::GetAnyChromePath(false /* is_sxs */);
205 if (!chrome_path
.empty()) {
206 base::CommandLine
cmd(chrome_path
);
207 CopyChromeSwitchesFromCurrentProcess(&cmd
);
209 // Required switches.
210 cmd
.AppendSwitchASCII(switches::kProcessType
, switches::kServiceProcess
);
211 cmd
.AppendSwitchPath(switches::kUserDataDir
, user_data_
);
212 cmd
.AppendSwitch(switches::kNoServiceAutorun
);
215 cmd
.AppendSwitch(switches::kAutoLaunchAtStartup
);
216 cmd
.AppendSwitch(switches::kDisableDefaultApps
);
217 cmd
.AppendSwitch(switches::kDisableExtensions
);
218 cmd
.AppendSwitch(switches::kDisableGpu
);
219 cmd
.AppendSwitch(switches::kDisableSoftwareRasterizer
);
220 cmd
.AppendSwitch(switches::kDisableSync
);
221 cmd
.AppendSwitch(switches::kNoFirstRun
);
222 cmd
.AppendSwitch(switches::kNoStartupWindow
);
224 base::win::ScopedHandle chrome_handle
;
225 base::Time started
= base::Time::Now();
227 LaunchProcess(cmd
, &chrome_handle
, &thread_id
);
228 base::Process chrome_process
;
229 if (chrome_handle
.IsValid())
230 chrome_process
= base::Process(chrome_handle
.Take());
232 HANDLE handles
[] = { stop_event_
.handle(), chrome_process
.Handle() };
233 DWORD wait_result
= WAIT_TIMEOUT
;
234 while (wait_result
== WAIT_TIMEOUT
) {
235 cloud_print::SetGoogleUpdateUsage(kGoogleUpdateId
);
236 wait_result
= ::WaitForMultipleObjects(arraysize(handles
), handles
,
237 FALSE
, kUsageUpdateTimeoutMs
);
239 if (wait_result
== WAIT_OBJECT_0
) {
240 ShutdownChrome(chrome_process
.Pass(), thread_id
);
242 } else if (wait_result
== WAIT_OBJECT_0
+ 1) {
243 LOG(ERROR
) << "Chrome process exited.";
245 LOG(ERROR
) << "Error waiting Chrome (" << ::GetLastError() << ").";
247 if (base::Time::Now() - started
> base::TimeDelta::FromHours(1)) {
248 // Reset timeout because process worked long enough.
249 time_out
= default_time_out
;
252 if (stop_event_
.TimedWait(time_out
))
257 std::string
ChromeLauncher::CreateServiceStateFile(
258 const std::string
& proxy_id
,
259 const std::vector
<std::string
>& printers
) {
260 base::ScopedTempDir temp_user_data
;
261 if (!temp_user_data
.CreateUniqueTempDir()) {
262 LOG(ERROR
) << "Can't create temp dir.";
263 return std::string();
266 base::FilePath chrome_path
=
267 chrome_launcher_support::GetAnyChromePath(false /* is_sxs */);
268 if (chrome_path
.empty()) {
269 LOG(ERROR
) << "Can't find Chrome.";
270 return std::string();
273 base::FilePath printers_file
= temp_user_data
.path().Append(L
"printers.json");
275 base::ListValue printer_list
;
276 printer_list
.AppendStrings(printers
);
277 std::string printers_json
;
278 base::JSONWriter::Write(printer_list
, &printers_json
);
279 size_t written
= base::WriteFile(printers_file
,
280 printers_json
.c_str(),
281 printers_json
.size());
282 if (written
!= printers_json
.size()) {
283 LOG(ERROR
) << "Can't write file.";
284 return std::string();
287 base::CommandLine
cmd(chrome_path
);
288 CopyChromeSwitchesFromCurrentProcess(&cmd
);
289 cmd
.AppendSwitchPath(switches::kUserDataDir
, temp_user_data
.path());
290 cmd
.AppendSwitchPath(switches::kCloudPrintSetupProxy
, printers_file
);
291 cmd
.AppendSwitch(switches::kNoServiceAutorun
);
294 cmd
.AppendSwitch(switches::kDisableDefaultApps
);
295 cmd
.AppendSwitch(switches::kDisableExtensions
);
296 cmd
.AppendSwitch(switches::kDisableSync
);
297 cmd
.AppendSwitch(switches::kNoDefaultBrowserCheck
);
298 cmd
.AppendSwitch(switches::kNoFirstRun
);
301 cloud_devices::GetCloudPrintEnableWithSigninURL(proxy_id
).spec());
303 base::win::ScopedHandle chrome_handle
;
305 if (!LaunchProcess(cmd
, &chrome_handle
, &thread_id
)) {
306 LOG(ERROR
) << "Unable to launch Chrome.";
307 return std::string();
309 base::Process
chrome_process(chrome_handle
.Take());
312 DWORD wait_result
= ::WaitForSingleObject(chrome_process
.Handle(), 500);
313 std::string json
= ReadAndUpdateServiceState(temp_user_data
.path(),
315 if (wait_result
== WAIT_OBJECT_0
) {
316 // Return what we have because browser is closed.
319 if (wait_result
!= WAIT_TIMEOUT
) {
320 LOG(ERROR
) << "Chrome launch failed.";
321 return std::string();
324 // Close chrome because Service State is ready.
325 CloseChrome(chrome_process
.Pass(), thread_id
);