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/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/kill.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(HANDLE process
, DWORD thread_id
) {
41 if (::PostThreadMessage(thread_id
, WM_QUIT
, 0, 0) &&
42 WAIT_OBJECT_0
== ::WaitForSingleObject(process
, kShutdownTimeoutMs
)) {
45 LOG(ERROR
) << "Failed to shutdown process.";
46 base::KillProcess(process
, 0, true);
49 BOOL CALLBACK
CloseIfPidEqual(HWND wnd
, LPARAM lparam
) {
51 ::GetWindowThreadProcessId(wnd
, &pid
);
52 if (pid
== static_cast<DWORD
>(lparam
))
53 ::PostMessage(wnd
, WM_CLOSE
, 0, 0);
57 void CloseAllProcessWindows(HANDLE process
) {
58 ::EnumWindows(&CloseIfPidEqual
, GetProcessId(process
));
61 // Close Chrome browser window.
62 void CloseChrome(HANDLE process
, DWORD thread_id
) {
63 CloseAllProcessWindows(process
);
64 if (WAIT_OBJECT_0
== ::WaitForSingleObject(process
, kShutdownTimeoutMs
)) {
67 ShutdownChrome(process
, thread_id
);
70 bool LaunchProcess(const CommandLine
& cmdline
,
71 base::win::ScopedHandle
* process_handle
,
73 STARTUPINFO startup_info
= {};
74 startup_info
.cb
= sizeof(startup_info
);
75 startup_info
.dwFlags
= STARTF_USESHOWWINDOW
;
76 startup_info
.wShowWindow
= SW_SHOW
;
78 PROCESS_INFORMATION temp_process_info
= {};
79 if (!CreateProcess(NULL
,
80 const_cast<wchar_t*>(cmdline
.GetCommandLineString().c_str()), NULL
, NULL
,
81 FALSE
, 0, NULL
, NULL
, &startup_info
, &temp_process_info
)) {
84 base::win::ScopedProcessInformation
process_info(temp_process_info
);
87 process_handle
->Set(process_info
.TakeProcessHandle());
90 *thread_id
= process_info
.thread_id();
95 std::string
ReadAndUpdateServiceState(const base::FilePath
& directory
,
96 const std::string
& proxy_id
) {
98 base::FilePath file_path
= directory
.Append(chrome::kServiceStateFileName
);
99 if (!base::ReadFileToString(file_path
, &json
)) {
100 return std::string();
103 scoped_ptr
<base::Value
> service_state(base::JSONReader::Read(json
));
104 base::DictionaryValue
* dictionary
= NULL
;
105 if (!service_state
->GetAsDictionary(&dictionary
) || !dictionary
) {
106 return std::string();
109 bool enabled
= false;
110 if (!dictionary
->GetBoolean(prefs::kCloudPrintProxyEnabled
, &enabled
) ||
112 return std::string();
115 std::string refresh_token
;
116 if (!dictionary
->GetString(prefs::kCloudPrintRobotRefreshToken
,
118 refresh_token
.empty()) {
119 return std::string();
122 // Remove everything except kCloudPrintRoot.
123 scoped_ptr
<base::Value
> cloud_print_root
;
124 dictionary
->Remove(prefs::kCloudPrintRoot
, &cloud_print_root
);
126 dictionary
->Set(prefs::kCloudPrintRoot
, cloud_print_root
.release());
128 dictionary
->SetBoolean(prefs::kCloudPrintXmppPingEnabled
, true);
129 if (!proxy_id
.empty()) // Reuse proxy id if we already had one.
130 dictionary
->SetString(prefs::kCloudPrintProxyId
, proxy_id
);
132 base::JSONWriter::WriteWithOptions(dictionary
,
133 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
138 void DeleteAutorunKeys(const base::FilePath
& user_data_dir
) {
139 base::win::RegKey
key(HKEY_CURRENT_USER
, kAutoRunKeyPath
, KEY_SET_VALUE
);
142 std::vector
<base::string16
> to_delete
;
144 base::FilePath abs_user_data_dir
= base::MakeAbsoluteFilePath(user_data_dir
);
147 base::win::RegistryValueIterator
value(HKEY_CURRENT_USER
, kAutoRunKeyPath
);
148 for (; value
.Valid(); ++value
) {
149 if (value
.Type() == REG_SZ
&& value
.Value()) {
150 CommandLine cmd
= CommandLine::FromString(value
.Value());
151 if (cmd
.GetSwitchValueASCII(switches::kProcessType
) ==
152 switches::kServiceProcess
&&
153 cmd
.HasSwitch(switches::kUserDataDir
)) {
154 base::FilePath path_from_reg
= base::MakeAbsoluteFilePath(
155 cmd
.GetSwitchValuePath(switches::kUserDataDir
));
156 if (path_from_reg
== abs_user_data_dir
) {
157 to_delete
.push_back(value
.Name());
164 for (size_t i
= 0; i
< to_delete
.size(); ++i
) {
165 key
.DeleteValue(to_delete
[i
].c_str());
171 ChromeLauncher::ChromeLauncher(const base::FilePath
& user_data
)
172 : stop_event_(true, true),
173 user_data_(user_data
) {
176 ChromeLauncher::~ChromeLauncher() {
179 bool ChromeLauncher::Start() {
180 DeleteAutorunKeys(user_data_
);
182 thread_
.reset(new base::DelegateSimpleThread(this, "chrome_launcher"));
187 void ChromeLauncher::Stop() {
188 stop_event_
.Signal();
193 void ChromeLauncher::Run() {
194 const base::TimeDelta default_time_out
= base::TimeDelta::FromSeconds(1);
195 const base::TimeDelta max_time_out
= base::TimeDelta::FromHours(1);
197 for (base::TimeDelta time_out
= default_time_out
;;
198 time_out
= std::min(time_out
* 2, max_time_out
)) {
199 base::FilePath chrome_path
= chrome_launcher_support::GetAnyChromePath();
201 if (!chrome_path
.empty()) {
202 CommandLine
cmd(chrome_path
);
203 CopyChromeSwitchesFromCurrentProcess(&cmd
);
205 // Required switches.
206 cmd
.AppendSwitchASCII(switches::kProcessType
, switches::kServiceProcess
);
207 cmd
.AppendSwitchPath(switches::kUserDataDir
, user_data_
);
208 cmd
.AppendSwitch(switches::kNoServiceAutorun
);
211 cmd
.AppendSwitch(switches::kAutoLaunchAtStartup
);
212 cmd
.AppendSwitch(switches::kDisableDefaultApps
);
213 cmd
.AppendSwitch(switches::kDisableExtensions
);
214 cmd
.AppendSwitch(switches::kDisableGpu
);
215 cmd
.AppendSwitch(switches::kDisableSoftwareRasterizer
);
216 cmd
.AppendSwitch(switches::kDisableSync
);
217 cmd
.AppendSwitch(switches::kNoFirstRun
);
218 cmd
.AppendSwitch(switches::kNoStartupWindow
);
220 base::win::ScopedHandle chrome_handle
;
221 base::Time started
= base::Time::Now();
223 LaunchProcess(cmd
, &chrome_handle
, &thread_id
);
225 HANDLE handles
[] = {stop_event_
.handle(), chrome_handle
};
226 DWORD wait_result
= WAIT_TIMEOUT
;
227 while (wait_result
== WAIT_TIMEOUT
) {
228 cloud_print::SetGoogleUpdateUsage(kGoogleUpdateId
);
229 wait_result
= ::WaitForMultipleObjects(arraysize(handles
), handles
,
230 FALSE
, kUsageUpdateTimeoutMs
);
232 if (wait_result
== WAIT_OBJECT_0
) {
233 ShutdownChrome(chrome_handle
, thread_id
);
235 } else if (wait_result
== WAIT_OBJECT_0
+ 1) {
236 LOG(ERROR
) << "Chrome process exited.";
238 LOG(ERROR
) << "Error waiting Chrome (" << ::GetLastError() << ").";
240 if (base::Time::Now() - started
> base::TimeDelta::FromHours(1)) {
241 // Reset timeout because process worked long enough.
242 time_out
= default_time_out
;
245 if (stop_event_
.TimedWait(time_out
))
250 std::string
ChromeLauncher::CreateServiceStateFile(
251 const std::string
& proxy_id
,
252 const std::vector
<std::string
>& printers
) {
253 base::ScopedTempDir temp_user_data
;
254 if (!temp_user_data
.CreateUniqueTempDir()) {
255 LOG(ERROR
) << "Can't create temp dir.";
256 return std::string();
259 base::FilePath chrome_path
= chrome_launcher_support::GetAnyChromePath();
260 if (chrome_path
.empty()) {
261 LOG(ERROR
) << "Can't find Chrome.";
262 return std::string();
265 base::FilePath printers_file
= temp_user_data
.path().Append(L
"printers.json");
267 base::ListValue printer_list
;
268 printer_list
.AppendStrings(printers
);
269 std::string printers_json
;
270 base::JSONWriter::Write(&printer_list
, &printers_json
);
271 size_t written
= base::WriteFile(printers_file
,
272 printers_json
.c_str(),
273 printers_json
.size());
274 if (written
!= printers_json
.size()) {
275 LOG(ERROR
) << "Can't write file.";
276 return std::string();
279 CommandLine
cmd(chrome_path
);
280 CopyChromeSwitchesFromCurrentProcess(&cmd
);
281 cmd
.AppendSwitchPath(switches::kUserDataDir
, temp_user_data
.path());
282 cmd
.AppendSwitchPath(switches::kCloudPrintSetupProxy
, printers_file
);
283 cmd
.AppendSwitch(switches::kNoServiceAutorun
);
286 cmd
.AppendSwitch(switches::kDisableDefaultApps
);
287 cmd
.AppendSwitch(switches::kDisableExtensions
);
288 cmd
.AppendSwitch(switches::kDisableSync
);
289 cmd
.AppendSwitch(switches::kNoDefaultBrowserCheck
);
290 cmd
.AppendSwitch(switches::kNoFirstRun
);
293 cloud_devices::GetCloudPrintEnableWithSigninURL(proxy_id
).spec());
295 base::win::ScopedHandle chrome_handle
;
297 if (!LaunchProcess(cmd
, &chrome_handle
, &thread_id
)) {
298 LOG(ERROR
) << "Unable to launch Chrome.";
299 return std::string();
303 DWORD wait_result
= ::WaitForSingleObject(chrome_handle
, 500);
304 std::string json
= ReadAndUpdateServiceState(temp_user_data
.path(),
306 if (wait_result
== WAIT_OBJECT_0
) {
307 // Return what we have because browser is closed.
310 if (wait_result
!= WAIT_TIMEOUT
) {
311 LOG(ERROR
) << "Chrome launch failed.";
312 return std::string();
315 // Close chrome because Service State is ready.
316 CloseChrome(chrome_handle
, thread_id
);