• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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