1 // Copyright (c) 2011 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.
4
5 #include "chrome/browser/first_run/first_run.h"
6
7 #include <shlobj.h>
8 #include <windows.h>
9
10 #include <set>
11 #include <sstream>
12
13 #include "base/environment.h"
14 #include "base/file_util.h"
15 #include "base/path_service.h"
16 #include "base/string_number_conversions.h"
17 #include "base/string_split.h"
18 #include "base/stringprintf.h"
19 #include "base/utf_string_conversions.h"
20 #include "base/win/object_watcher.h"
21 #include "base/win/windows_version.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/extensions/extension_service.h"
24 #include "chrome/browser/extensions/extension_updater.h"
25 #include "chrome/browser/first_run/first_run_import_observer.h"
26 #include "chrome/browser/importer/importer_host.h"
27 #include "chrome/browser/importer/importer_list.h"
28 #include "chrome/browser/importer/importer_progress_dialog.h"
29 #include "chrome/browser/profiles/profile.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/worker_thread_ticker.h"
32 #include "chrome/installer/util/browser_distribution.h"
33 #include "chrome/installer/util/google_update_constants.h"
34 #include "chrome/installer/util/google_update_settings.h"
35 #include "chrome/installer/util/install_util.h"
36 #include "chrome/installer/util/shell_util.h"
37 #include "chrome/installer/util/util_constants.h"
38 #include "content/common/notification_service.h"
39 #include "content/common/result_codes.h"
40 #include "google_update_idl.h"
41 #include "grit/chromium_strings.h"
42 #include "grit/generated_resources.h"
43 #include "grit/locale_settings.h"
44 #include "grit/theme_resources.h"
45 #include "ui/base/resource/resource_bundle.h"
46 #include "ui/base/ui_base_switches.h"
47
48 namespace {
49
50 // Helper class that performs delayed first-run tasks that need more of the
51 // chrome infrastructure to be up and running before they can be attempted.
52 class FirstRunDelayedTasks : public NotificationObserver {
53 public:
54 enum Tasks {
55 NO_TASK,
56 INSTALL_EXTENSIONS
57 };
58
FirstRunDelayedTasks(Tasks task)59 explicit FirstRunDelayedTasks(Tasks task) {
60 if (task == INSTALL_EXTENSIONS) {
61 registrar_.Add(this, NotificationType::EXTENSIONS_READY,
62 NotificationService::AllSources());
63 }
64 registrar_.Add(this, NotificationType::BROWSER_CLOSED,
65 NotificationService::AllSources());
66 }
67
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)68 virtual void Observe(NotificationType type,
69 const NotificationSource& source,
70 const NotificationDetails& details) {
71 // After processing the notification we always delete ourselves.
72 if (type.value == NotificationType::EXTENSIONS_READY)
73 DoExtensionWork(Source<Profile>(source).ptr()->GetExtensionService());
74 delete this;
75 return;
76 }
77
78 private:
79 // Private ctor forces it to be created only in the heap.
~FirstRunDelayedTasks()80 ~FirstRunDelayedTasks() {}
81
82 // The extension work is to basically trigger an extension update check.
83 // If the extension specified in the master pref is older than the live
84 // extension it will get updated which is the same as get it installed.
DoExtensionWork(ExtensionService * service)85 void DoExtensionWork(ExtensionService* service) {
86 if (!service)
87 return;
88 service->updater()->CheckNow();
89 return;
90 }
91
92 NotificationRegistrar registrar_;
93 };
94
95 // Creates the desktop shortcut to chrome for the current user. Returns
96 // false if it fails. It will overwrite the shortcut if it exists.
CreateChromeDesktopShortcut()97 bool CreateChromeDesktopShortcut() {
98 FilePath chrome_exe;
99 if (!PathService::Get(base::FILE_EXE, &chrome_exe))
100 return false;
101 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
102 if (!dist)
103 return false;
104 return ShellUtil::CreateChromeDesktopShortcut(
105 dist,
106 chrome_exe.value(),
107 dist->GetAppDescription(),
108 ShellUtil::CURRENT_USER,
109 false,
110 true); // create if doesn't exist.
111 }
112
113 // Creates the quick launch shortcut to chrome for the current user. Returns
114 // false if it fails. It will overwrite the shortcut if it exists.
CreateChromeQuickLaunchShortcut()115 bool CreateChromeQuickLaunchShortcut() {
116 FilePath chrome_exe;
117 if (!PathService::Get(base::FILE_EXE, &chrome_exe))
118 return false;
119 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
120 return ShellUtil::CreateChromeQuickLaunchShortcut(
121 dist,
122 chrome_exe.value(),
123 ShellUtil::CURRENT_USER, // create only for current user.
124 true); // create if doesn't exist.
125 }
126
127 } // namespace
128
LaunchSetupWithParam(const std::string & param,const std::wstring & value,int * ret_code)129 bool FirstRun::LaunchSetupWithParam(const std::string& param,
130 const std::wstring& value,
131 int* ret_code) {
132 FilePath exe_path;
133 if (!PathService::Get(base::DIR_MODULE, &exe_path))
134 return false;
135 exe_path = exe_path.Append(installer::kInstallerDir);
136 exe_path = exe_path.Append(installer::kSetupExe);
137 base::ProcessHandle ph;
138 CommandLine cl(exe_path);
139 cl.AppendSwitchNative(param, value);
140
141 CommandLine* browser_command_line = CommandLine::ForCurrentProcess();
142 if (browser_command_line->HasSwitch(switches::kChromeFrame)) {
143 cl.AppendSwitch(switches::kChromeFrame);
144 }
145
146 if (!base::LaunchApp(cl, false, false, &ph))
147 return false;
148 DWORD wr = ::WaitForSingleObject(ph, INFINITE);
149 if (wr != WAIT_OBJECT_0)
150 return false;
151 return (TRUE == ::GetExitCodeProcess(ph, reinterpret_cast<DWORD*>(ret_code)));
152 }
153
WriteEULAtoTempFile(FilePath * eula_path)154 bool FirstRun::WriteEULAtoTempFile(FilePath* eula_path) {
155 base::StringPiece terms =
156 ResourceBundle::GetSharedInstance().GetRawDataResource(IDR_TERMS_HTML);
157 if (terms.empty())
158 return false;
159 FILE *file = file_util::CreateAndOpenTemporaryFile(eula_path);
160 if (!file)
161 return false;
162 bool good = fwrite(terms.data(), terms.size(), 1, file) == 1;
163 fclose(file);
164 return good;
165 }
166
DoDelayedInstallExtensions()167 void FirstRun::DoDelayedInstallExtensions() {
168 new FirstRunDelayedTasks(FirstRunDelayedTasks::INSTALL_EXTENSIONS);
169 }
170
171 namespace {
172
173 // This class is used by FirstRun::ImportSettings to determine when the import
174 // process has ended and what was the result of the operation as reported by
175 // the process exit code. This class executes in the context of the main chrome
176 // process.
177 class ImportProcessRunner : public base::win::ObjectWatcher::Delegate {
178 public:
179 // The constructor takes the importer process to watch and then it does a
180 // message loop blocking wait until the process ends. This object now owns
181 // the import_process handle.
ImportProcessRunner(base::ProcessHandle import_process)182 explicit ImportProcessRunner(base::ProcessHandle import_process)
183 : import_process_(import_process),
184 exit_code_(ResultCodes::NORMAL_EXIT) {
185 watcher_.StartWatching(import_process, this);
186 MessageLoop::current()->Run();
187 }
~ImportProcessRunner()188 virtual ~ImportProcessRunner() {
189 ::CloseHandle(import_process_);
190 }
191 // Returns the child process exit code. There are 2 expected values:
192 // NORMAL_EXIT, or IMPORTER_HUNG.
exit_code() const193 int exit_code() const { return exit_code_; }
194
195 // The child process has terminated. Find the exit code and quit the loop.
OnObjectSignaled(HANDLE object)196 virtual void OnObjectSignaled(HANDLE object) {
197 DCHECK(object == import_process_);
198 if (!::GetExitCodeProcess(import_process_, &exit_code_)) {
199 NOTREACHED();
200 }
201 MessageLoop::current()->Quit();
202 }
203
204 private:
205 base::win::ObjectWatcher watcher_;
206 base::ProcessHandle import_process_;
207 DWORD exit_code_;
208 };
209
210 // Check every 3 seconds if the importer UI has hung.
211 const int kPollHangFrequency = 3000;
212
213 // This class specializes on finding hung 'owned' windows. Unfortunately, the
214 // HungWindowDetector class cannot be used here because it assumes child
215 // windows and not owned top-level windows.
216 // This code is executed in the context of the main browser process and will
217 // terminate the importer process if it is hung.
218 class HungImporterMonitor : public WorkerThreadTicker::Callback {
219 public:
220 // The ctor takes the owner popup window and the process handle of the
221 // process to kill in case the popup or its owned active popup become
222 // unresponsive.
HungImporterMonitor(HWND owner_window,base::ProcessHandle import_process)223 HungImporterMonitor(HWND owner_window, base::ProcessHandle import_process)
224 : owner_window_(owner_window),
225 import_process_(import_process),
226 ticker_(kPollHangFrequency) {
227 ticker_.RegisterTickHandler(this);
228 ticker_.Start();
229 }
~HungImporterMonitor()230 virtual ~HungImporterMonitor() {
231 ticker_.Stop();
232 ticker_.UnregisterTickHandler(this);
233 }
234
235 private:
OnTick()236 virtual void OnTick() {
237 if (!import_process_)
238 return;
239 // We find the top active popup that we own, this will be either the
240 // owner_window_ itself or the dialog window of the other process. In
241 // both cases it is worth hung testing because both windows share the
242 // same message queue and at some point the other window could be gone
243 // while the other process still not pumping messages.
244 HWND active_window = ::GetLastActivePopup(owner_window_);
245 if (::IsHungAppWindow(active_window) || ::IsHungAppWindow(owner_window_)) {
246 ::TerminateProcess(import_process_, ResultCodes::IMPORTER_HUNG);
247 import_process_ = NULL;
248 }
249 }
250
251 HWND owner_window_;
252 base::ProcessHandle import_process_;
253 WorkerThreadTicker ticker_;
254 DISALLOW_COPY_AND_ASSIGN(HungImporterMonitor);
255 };
256
EncodeImportParams(int importer_type,int options,int skip_first_run_ui,HWND window)257 std::string EncodeImportParams(int importer_type,
258 int options,
259 int skip_first_run_ui,
260 HWND window) {
261 return base::StringPrintf(
262 "%d@%d@%d@%d", importer_type, options, skip_first_run_ui, window);
263 }
264
DecodeImportParams(const std::string & encoded,int * importer_type,int * options,int * skip_first_run_ui,HWND * window)265 bool DecodeImportParams(const std::string& encoded,
266 int* importer_type,
267 int* options,
268 int* skip_first_run_ui,
269 HWND* window) {
270 std::vector<std::string> parts;
271 base::SplitString(encoded, '@', &parts);
272 if (parts.size() != 4)
273 return false;
274
275 if (!base::StringToInt(parts[0], importer_type))
276 return false;
277
278 if (!base::StringToInt(parts[1], options))
279 return false;
280
281 if (!base::StringToInt(parts[2], skip_first_run_ui))
282 return false;
283
284 int64 window_int;
285 base::StringToInt64(parts[3], &window_int);
286 *window = reinterpret_cast<HWND>(window_int);
287 return true;
288 }
289
290 } // namespace
291
292 // static
PlatformSetup()293 void FirstRun::PlatformSetup() {
294 CreateChromeDesktopShortcut();
295 // Windows 7 has deprecated the quick launch bar.
296 if (base::win::GetVersion() < base::win::VERSION_WIN7)
297 CreateChromeQuickLaunchShortcut();
298 }
299
300 // static
IsOrganicFirstRun()301 bool FirstRun::IsOrganicFirstRun() {
302 std::wstring brand;
303 GoogleUpdateSettings::GetBrand(&brand);
304 return GoogleUpdateSettings::IsOrganicFirstRun(brand);
305 }
306
307 // static
ImportSettings(Profile * profile,int importer_type,int items_to_import,const FilePath & import_bookmarks_path,bool skip_first_run_ui,HWND parent_window)308 bool FirstRun::ImportSettings(Profile* profile,
309 int importer_type,
310 int items_to_import,
311 const FilePath& import_bookmarks_path,
312 bool skip_first_run_ui,
313 HWND parent_window) {
314 const CommandLine& cmdline = *CommandLine::ForCurrentProcess();
315 CommandLine import_cmd(cmdline.GetProgram());
316
317 const char* kSwitchNames[] = {
318 switches::kUserDataDir,
319 switches::kChromeFrame,
320 switches::kCountry,
321 };
322 import_cmd.CopySwitchesFrom(cmdline, kSwitchNames, arraysize(kSwitchNames));
323
324 // Since ImportSettings is called before the local state is stored on disk
325 // we pass the language as an argument. GetApplicationLocale checks the
326 // current command line as fallback.
327 import_cmd.AppendSwitchASCII(switches::kLang,
328 g_browser_process->GetApplicationLocale());
329
330 if (items_to_import) {
331 import_cmd.CommandLine::AppendSwitchASCII(switches::kImport,
332 EncodeImportParams(importer_type, items_to_import,
333 skip_first_run_ui ? 1 : 0, NULL));
334 }
335
336 if (!import_bookmarks_path.empty()) {
337 import_cmd.CommandLine::AppendSwitchPath(
338 switches::kImportFromFile, import_bookmarks_path);
339 }
340
341 // Time to launch the process that is going to do the import.
342 base::ProcessHandle import_process;
343 if (!base::LaunchApp(import_cmd, false, false, &import_process))
344 return false;
345
346 // We block inside the import_runner ctor, pumping messages until the
347 // importer process ends. This can happen either by completing the import
348 // or by hang_monitor killing it.
349 ImportProcessRunner import_runner(import_process);
350
351 // Import process finished. Reload the prefs, because importer may set
352 // the pref value.
353 if (profile)
354 profile->GetPrefs()->ReloadPersistentPrefs();
355
356 return (import_runner.exit_code() == ResultCodes::NORMAL_EXIT);
357 }
358
359 // static
ImportSettings(Profile * profile,scoped_refptr<ImporterHost> importer_host,scoped_refptr<ImporterList> importer_list,int items_to_import)360 bool FirstRun::ImportSettings(Profile* profile,
361 scoped_refptr<ImporterHost> importer_host,
362 scoped_refptr<ImporterList> importer_list,
363 int items_to_import) {
364 return ImportSettings(
365 profile,
366 importer_list->GetSourceProfileAt(0).importer_type,
367 items_to_import,
368 FilePath(),
369 false,
370 NULL);
371 }
372
ImportFromBrowser(Profile * profile,const CommandLine & cmdline)373 int FirstRun::ImportFromBrowser(Profile* profile,
374 const CommandLine& cmdline) {
375 std::string import_info = cmdline.GetSwitchValueASCII(switches::kImport);
376 if (import_info.empty()) {
377 NOTREACHED();
378 return false;
379 }
380 int importer_type = 0;
381 int items_to_import = 0;
382 int skip_first_run_ui = 0;
383 HWND parent_window = NULL;
384 if (!DecodeImportParams(import_info, &importer_type, &items_to_import,
385 &skip_first_run_ui, &parent_window)) {
386 NOTREACHED();
387 return false;
388 }
389 scoped_refptr<ImporterHost> importer_host(new ImporterHost);
390 FirstRunImportObserver importer_observer;
391
392 scoped_refptr<ImporterList> importer_list(new ImporterList);
393 importer_list->DetectSourceProfilesHack();
394
395 // If |skip_first_run_ui|, we run in headless mode. This means that if
396 // there is user action required the import is automatically canceled.
397 if (skip_first_run_ui > 0)
398 importer_host->set_headless();
399
400 importer::ShowImportProgressDialog(
401 parent_window,
402 static_cast<uint16>(items_to_import),
403 importer_host,
404 &importer_observer,
405 importer_list->GetSourceProfileForImporterType(importer_type),
406 profile,
407 true);
408 importer_observer.RunLoop();
409 return importer_observer.import_result();
410 }
411