Home | History | Annotate | Download | only in first_run
      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 
     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.
     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.
    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 
    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 
    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 
    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.
    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.
    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.
    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 
    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 
    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
    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
    301 bool FirstRun::IsOrganicFirstRun() {
    302   std::wstring brand;
    303   GoogleUpdateSettings::GetBrand(&brand);
    304   return GoogleUpdateSettings::IsOrganicFirstRun(brand);
    305 }
    306 
    307 // static
    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
    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 
    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