Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / cloud_print / service / win / chrome_launcher.cc
blob8c005566d01b25957bf88de7830bd10a0c6fc81a
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"
29 #include "url/gurl.h"
31 namespace {
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)) {
44 return;
46 LOG(ERROR) << "Failed to shutdown process.";
47 process.Terminate(0, true);
50 BOOL CALLBACK CloseIfPidEqual(HWND wnd, LPARAM lparam) {
51 DWORD pid = 0;
52 ::GetWindowThreadProcessId(wnd, &pid);
53 if (pid == static_cast<DWORD>(lparam))
54 ::PostMessage(wnd, WM_CLOSE, 0, 0);
55 return TRUE;
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());
65 if (WAIT_OBJECT_0 ==
66 ::WaitForSingleObject(process.Handle(), kShutdownTimeoutMs)) {
67 return;
69 ShutdownChrome(process.Pass(), thread_id);
72 bool LaunchProcess(const base::CommandLine& cmdline,
73 base::win::ScopedHandle* process_handle,
74 DWORD* thread_id) {
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)) {
86 return false;
88 base::win::ScopedProcessInformation process_info(temp_process_info);
90 if (process_handle)
91 process_handle->Set(process_info.TakeProcessHandle());
93 if (thread_id)
94 *thread_id = process_info.thread_id();
96 return true;
99 std::string ReadAndUpdateServiceState(const base::FilePath& directory,
100 const std::string& proxy_id) {
101 std::string json;
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) ||
115 !enabled) {
116 return std::string();
119 std::string refresh_token;
120 if (!dictionary->GetString(prefs::kCloudPrintRobotRefreshToken,
121 &refresh_token) ||
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);
129 dictionary->Clear();
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);
135 std::string result;
136 base::JSONWriter::WriteWithOptions(*dictionary,
137 base::JSONWriter::OPTIONS_PRETTY_PRINT,
138 &result);
139 return result;
142 void DeleteAutorunKeys(const base::FilePath& user_data_dir) {
143 base::win::RegKey key(HKEY_CURRENT_USER, kAutoRunKeyPath, KEY_SET_VALUE);
144 if (!key.Valid())
145 return;
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());
173 } // namespace
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_);
184 stop_event_.Reset();
185 thread_.reset(new base::DelegateSimpleThread(this, "chrome_launcher"));
186 thread_->Start();
187 return true;
190 void ChromeLauncher::Stop() {
191 stop_event_.Signal();
192 thread_->Join();
193 thread_.reset();
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);
214 // Optional.
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();
226 DWORD thread_id = 0;
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);
241 break;
242 } else if (wait_result == WAIT_OBJECT_0 + 1) {
243 LOG(ERROR) << "Chrome process exited.";
244 } else {
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))
253 break;
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);
293 // Optional.
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);
300 cmd.AppendArg(
301 cloud_devices::GetCloudPrintEnableWithSigninURL(proxy_id).spec());
303 base::win::ScopedHandle chrome_handle;
304 DWORD thread_id = 0;
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());
311 for (;;) {
312 DWORD wait_result = ::WaitForSingleObject(chrome_process.Handle(), 500);
313 std::string json = ReadAndUpdateServiceState(temp_user_data.path(),
314 proxy_id);
315 if (wait_result == WAIT_OBJECT_0) {
316 // Return what we have because browser is closed.
317 return json;
319 if (wait_result != WAIT_TIMEOUT) {
320 LOG(ERROR) << "Chrome launch failed.";
321 return std::string();
323 if (!json.empty()) {
324 // Close chrome because Service State is ready.
325 CloseChrome(chrome_process.Pass(), thread_id);
326 return json;