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
48namespace {
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.
52class FirstRunDelayedTasks : public NotificationObserver {
53 public:
54  enum Tasks {
55    NO_TASK,
56    INSTALL_EXTENSIONS
57  };
58
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
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.
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.
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.
97bool 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.
115bool 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
129bool 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
154bool 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
167void FirstRun::DoDelayedInstallExtensions() {
168  new FirstRunDelayedTasks(FirstRunDelayedTasks::INSTALL_EXTENSIONS);
169}
170
171namespace {
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.
177class 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.
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  }
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.
193  int exit_code() const { return exit_code_; }
194
195  // The child process has terminated. Find the exit code and quit the loop.
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.
211const 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.
218class 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.
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  }
230  virtual ~HungImporterMonitor() {
231    ticker_.Stop();
232    ticker_.UnregisterTickHandler(this);
233  }
234
235 private:
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
257std::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
265bool 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
293void 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
301bool FirstRun::IsOrganicFirstRun() {
302  std::wstring brand;
303  GoogleUpdateSettings::GetBrand(&brand);
304  return GoogleUpdateSettings::IsOrganicFirstRun(brand);
305}
306
307// static
308bool 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
360bool 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
373int 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