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