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/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 base::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 base::FilePath::StringType
writable_cmdline_str(
80 cmdline
.GetCommandLineString());
81 if (!CreateProcess(NULL
,
82 &writable_cmdline_str
[0], NULL
, NULL
,
83 FALSE
, 0, NULL
, NULL
, &startup_info
, &temp_process_info
)) {
86 base::win::ScopedProcessInformation
process_info(temp_process_info
);
89 process_handle
->Set(process_info
.TakeProcessHandle());
92 *thread_id
= process_info
.thread_id();
97 std::string
ReadAndUpdateServiceState(const base::FilePath
& directory
,
98 const std::string
& proxy_id
) {
100 base::FilePath file_path
= directory
.Append(chrome::kServiceStateFileName
);
101 if (!base::ReadFileToString(file_path
, &json
)) {
102 return std::string();
105 scoped_ptr
<base::Value
> service_state(base::JSONReader::Read(json
));
106 base::DictionaryValue
* dictionary
= NULL
;
107 if (!service_state
->GetAsDictionary(&dictionary
) || !dictionary
) {
108 return std::string();
111 bool enabled
= false;
112 if (!dictionary
->GetBoolean(prefs::kCloudPrintProxyEnabled
, &enabled
) ||
114 return std::string();
117 std::string refresh_token
;
118 if (!dictionary
->GetString(prefs::kCloudPrintRobotRefreshToken
,
120 refresh_token
.empty()) {
121 return std::string();
124 // Remove everything except kCloudPrintRoot.
125 scoped_ptr
<base::Value
> cloud_print_root
;
126 dictionary
->Remove(prefs::kCloudPrintRoot
, &cloud_print_root
);
128 dictionary
->Set(prefs::kCloudPrintRoot
, cloud_print_root
.release());
130 dictionary
->SetBoolean(prefs::kCloudPrintXmppPingEnabled
, true);
131 if (!proxy_id
.empty()) // Reuse proxy id if we already had one.
132 dictionary
->SetString(prefs::kCloudPrintProxyId
, proxy_id
);
134 base::JSONWriter::WriteWithOptions(dictionary
,
135 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
140 void DeleteAutorunKeys(const base::FilePath
& user_data_dir
) {
141 base::win::RegKey
key(HKEY_CURRENT_USER
, kAutoRunKeyPath
, KEY_SET_VALUE
);
144 std::vector
<base::string16
> to_delete
;
146 base::FilePath abs_user_data_dir
= base::MakeAbsoluteFilePath(user_data_dir
);
149 base::win::RegistryValueIterator
value(HKEY_CURRENT_USER
, kAutoRunKeyPath
);
150 for (; value
.Valid(); ++value
) {
151 if (value
.Type() == REG_SZ
&& value
.Value()) {
152 base::CommandLine cmd
= base::CommandLine::FromString(value
.Value());
153 if (cmd
.GetSwitchValueASCII(switches::kProcessType
) ==
154 switches::kServiceProcess
&&
155 cmd
.HasSwitch(switches::kUserDataDir
)) {
156 base::FilePath path_from_reg
= base::MakeAbsoluteFilePath(
157 cmd
.GetSwitchValuePath(switches::kUserDataDir
));
158 if (path_from_reg
== abs_user_data_dir
) {
159 to_delete
.push_back(value
.Name());
166 for (size_t i
= 0; i
< to_delete
.size(); ++i
) {
167 key
.DeleteValue(to_delete
[i
].c_str());
173 ChromeLauncher::ChromeLauncher(const base::FilePath
& user_data
)
174 : stop_event_(true, true),
175 user_data_(user_data
) {
178 ChromeLauncher::~ChromeLauncher() {
181 bool ChromeLauncher::Start() {
182 DeleteAutorunKeys(user_data_
);
184 thread_
.reset(new base::DelegateSimpleThread(this, "chrome_launcher"));
189 void ChromeLauncher::Stop() {
190 stop_event_
.Signal();
195 void ChromeLauncher::Run() {
196 const base::TimeDelta default_time_out
= base::TimeDelta::FromSeconds(1);
197 const base::TimeDelta max_time_out
= base::TimeDelta::FromHours(1);
199 for (base::TimeDelta time_out
= default_time_out
;;
200 time_out
= std::min(time_out
* 2, max_time_out
)) {
201 base::FilePath chrome_path
= chrome_launcher_support::GetAnyChromePath();
203 if (!chrome_path
.empty()) {
204 base::CommandLine
cmd(chrome_path
);
205 CopyChromeSwitchesFromCurrentProcess(&cmd
);
207 // Required switches.
208 cmd
.AppendSwitchASCII(switches::kProcessType
, switches::kServiceProcess
);
209 cmd
.AppendSwitchPath(switches::kUserDataDir
, user_data_
);
210 cmd
.AppendSwitch(switches::kNoServiceAutorun
);
213 cmd
.AppendSwitch(switches::kAutoLaunchAtStartup
);
214 cmd
.AppendSwitch(switches::kDisableDefaultApps
);
215 cmd
.AppendSwitch(switches::kDisableExtensions
);
216 cmd
.AppendSwitch(switches::kDisableGpu
);
217 cmd
.AppendSwitch(switches::kDisableSoftwareRasterizer
);
218 cmd
.AppendSwitch(switches::kDisableSync
);
219 cmd
.AppendSwitch(switches::kNoFirstRun
);
220 cmd
.AppendSwitch(switches::kNoStartupWindow
);
222 base::win::ScopedHandle chrome_handle
;
223 base::Time started
= base::Time::Now();
225 LaunchProcess(cmd
, &chrome_handle
, &thread_id
);
227 HANDLE handles
[] = { stop_event_
.handle(), chrome_handle
.Get() };
228 DWORD wait_result
= WAIT_TIMEOUT
;
229 while (wait_result
== WAIT_TIMEOUT
) {
230 cloud_print::SetGoogleUpdateUsage(kGoogleUpdateId
);
231 wait_result
= ::WaitForMultipleObjects(arraysize(handles
), handles
,
232 FALSE
, kUsageUpdateTimeoutMs
);
234 if (wait_result
== WAIT_OBJECT_0
) {
235 ShutdownChrome(chrome_handle
.Get(), thread_id
);
237 } else if (wait_result
== WAIT_OBJECT_0
+ 1) {
238 LOG(ERROR
) << "Chrome process exited.";
240 LOG(ERROR
) << "Error waiting Chrome (" << ::GetLastError() << ").";
242 if (base::Time::Now() - started
> base::TimeDelta::FromHours(1)) {
243 // Reset timeout because process worked long enough.
244 time_out
= default_time_out
;
247 if (stop_event_
.TimedWait(time_out
))
252 std::string
ChromeLauncher::CreateServiceStateFile(
253 const std::string
& proxy_id
,
254 const std::vector
<std::string
>& printers
) {
255 base::ScopedTempDir temp_user_data
;
256 if (!temp_user_data
.CreateUniqueTempDir()) {
257 LOG(ERROR
) << "Can't create temp dir.";
258 return std::string();
261 base::FilePath chrome_path
= chrome_launcher_support::GetAnyChromePath();
262 if (chrome_path
.empty()) {
263 LOG(ERROR
) << "Can't find Chrome.";
264 return std::string();
267 base::FilePath printers_file
= temp_user_data
.path().Append(L
"printers.json");
269 base::ListValue printer_list
;
270 printer_list
.AppendStrings(printers
);
271 std::string printers_json
;
272 base::JSONWriter::Write(&printer_list
, &printers_json
);
273 size_t written
= base::WriteFile(printers_file
,
274 printers_json
.c_str(),
275 printers_json
.size());
276 if (written
!= printers_json
.size()) {
277 LOG(ERROR
) << "Can't write file.";
278 return std::string();
281 base::CommandLine
cmd(chrome_path
);
282 CopyChromeSwitchesFromCurrentProcess(&cmd
);
283 cmd
.AppendSwitchPath(switches::kUserDataDir
, temp_user_data
.path());
284 cmd
.AppendSwitchPath(switches::kCloudPrintSetupProxy
, printers_file
);
285 cmd
.AppendSwitch(switches::kNoServiceAutorun
);
288 cmd
.AppendSwitch(switches::kDisableDefaultApps
);
289 cmd
.AppendSwitch(switches::kDisableExtensions
);
290 cmd
.AppendSwitch(switches::kDisableSync
);
291 cmd
.AppendSwitch(switches::kNoDefaultBrowserCheck
);
292 cmd
.AppendSwitch(switches::kNoFirstRun
);
295 cloud_devices::GetCloudPrintEnableWithSigninURL(proxy_id
).spec());
297 base::win::ScopedHandle chrome_handle
;
299 if (!LaunchProcess(cmd
, &chrome_handle
, &thread_id
)) {
300 LOG(ERROR
) << "Unable to launch Chrome.";
301 return std::string();
305 DWORD wait_result
= ::WaitForSingleObject(chrome_handle
.Get(), 500);
306 std::string json
= ReadAndUpdateServiceState(temp_user_data
.path(),
308 if (wait_result
== WAIT_OBJECT_0
) {
309 // Return what we have because browser is closed.
312 if (wait_result
!= WAIT_TIMEOUT
) {
313 LOG(ERROR
) << "Chrome launch failed.";
314 return std::string();
317 // Close chrome because Service State is ready.
318 CloseChrome(chrome_handle
.Get(), thread_id
);