OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/first_run/first_run.h" | 5 #include "chrome/browser/first_run/first_run.h" |
6 | 6 |
| 7 #include <windows.h> |
7 #include <shellapi.h> | 8 #include <shellapi.h> |
| 9 #include <shlobj.h> |
8 | 10 |
9 #include "base/base_paths.h" | |
10 #include "base/callback.h" | 11 #include "base/callback.h" |
| 12 #include "base/environment.h" |
11 #include "base/file_util.h" | 13 #include "base/file_util.h" |
12 #include "base/files/file_path.h" | 14 #include "base/files/file_path.h" |
13 #include "base/path_service.h" | 15 #include "base/path_service.h" |
14 #include "base/prefs/pref_service.h" | 16 #include "base/prefs/pref_service.h" |
15 #include "base/process.h" | |
16 #include "base/process_util.h" | 17 #include "base/process_util.h" |
| 18 #include "base/stringprintf.h" |
| 19 #include "base/strings/string_number_conversions.h" |
| 20 #include "base/strings/string_split.h" |
17 #include "base/threading/sequenced_worker_pool.h" | 21 #include "base/threading/sequenced_worker_pool.h" |
18 #include "base/time.h" | 22 #include "base/time.h" |
| 23 #include "base/utf_string_conversions.h" |
19 #include "base/win/metro.h" | 24 #include "base/win/metro.h" |
| 25 #include "base/win/object_watcher.h" |
| 26 #include "base/win/windows_version.h" |
| 27 #include "chrome/browser/browser_process.h" |
20 #include "chrome/browser/first_run/first_run_internal.h" | 28 #include "chrome/browser/first_run/first_run_internal.h" |
| 29 #include "chrome/browser/importer/importer_host.h" |
| 30 #include "chrome/browser/importer/importer_list.h" |
| 31 #include "chrome/browser/process_singleton.h" |
| 32 #include "chrome/browser/profiles/profile.h" |
| 33 #include "chrome/browser/shell_integration.h" |
21 #include "chrome/common/chrome_constants.h" | 34 #include "chrome/common/chrome_constants.h" |
22 #include "chrome/common/chrome_paths.h" | 35 #include "chrome/common/chrome_paths.h" |
| 36 #include "chrome/common/chrome_result_codes.h" |
23 #include "chrome/common/chrome_switches.h" | 37 #include "chrome/common/chrome_switches.h" |
| 38 #include "chrome/common/pref_names.h" |
| 39 #include "chrome/common/worker_thread_ticker.h" |
| 40 #include "chrome/installer/util/browser_distribution.h" |
24 #include "chrome/installer/util/google_update_settings.h" | 41 #include "chrome/installer/util/google_update_settings.h" |
25 #include "chrome/installer/util/install_util.h" | 42 #include "chrome/installer/util/install_util.h" |
26 #include "chrome/installer/util/master_preferences.h" | 43 #include "chrome/installer/util/master_preferences.h" |
27 #include "chrome/installer/util/master_preferences_constants.h" | 44 #include "chrome/installer/util/master_preferences_constants.h" |
| 45 #include "chrome/installer/util/shell_util.h" |
28 #include "chrome/installer/util/util_constants.h" | 46 #include "chrome/installer/util/util_constants.h" |
29 #include "content/public/browser/browser_thread.h" | 47 #include "content/public/browser/browser_thread.h" |
| 48 #include "content/public/browser/notification_service.h" |
| 49 #include "content/public/browser/user_metrics.h" |
30 #include "google_update/google_update_idl.h" | 50 #include "google_update/google_update_idl.h" |
| 51 #include "grit/chromium_strings.h" |
| 52 #include "grit/generated_resources.h" |
31 #include "grit/locale_settings.h" | 53 #include "grit/locale_settings.h" |
| 54 #include "grit/theme_resources.h" |
32 #include "ui/base/l10n/l10n_util.h" | 55 #include "ui/base/l10n/l10n_util.h" |
| 56 #include "ui/base/layout.h" |
| 57 #include "ui/base/ui_base_switches.h" |
33 #include "ui/base/win/shell.h" | 58 #include "ui/base/win/shell.h" |
34 | 59 |
35 namespace { | 60 namespace { |
36 | 61 |
37 // Launches the setup exe with the given parameter/value on the command-line. | 62 // Launches the setup exe with the given parameter/value on the command-line. |
38 // For non-metro Windows, it waits for its termination, returns its exit code | 63 // For non-metro Windows, it waits for its termination, returns its exit code |
39 // in |*ret_code|, and returns true if the exit code is valid. | 64 // in |*ret_code|, and returns true if the exit code is valid. |
40 // For metro Windows, it launches setup via ShellExecuteEx and returns in order | 65 // For metro Windows, it launches setup via ShellExecuteEx and returns in order |
41 // to bounce the user back to the desktop, then returns immediately. | 66 // to bounce the user back to the desktop, then returns immediately. |
42 bool LaunchSetupForEula(const base::FilePath::StringType& value, | 67 bool LaunchSetupForEula(const base::FilePath::StringType& value, |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
140 // accepted. | 165 // accepted. |
141 bool CreateEULASentinel() { | 166 bool CreateEULASentinel() { |
142 base::FilePath eula_sentinel; | 167 base::FilePath eula_sentinel; |
143 if (!GetEULASentinelFilePath(&eula_sentinel)) | 168 if (!GetEULASentinelFilePath(&eula_sentinel)) |
144 return false; | 169 return false; |
145 | 170 |
146 return (file_util::CreateDirectory(eula_sentinel.DirName()) && | 171 return (file_util::CreateDirectory(eula_sentinel.DirName()) && |
147 file_util::WriteFile(eula_sentinel, "", 0) != -1); | 172 file_util::WriteFile(eula_sentinel, "", 0) != -1); |
148 } | 173 } |
149 | 174 |
| 175 // This class is used by first_run::ImportSettings to determine when the import |
| 176 // process has ended and what was the result of the operation as reported by |
| 177 // the process exit code. This class executes in the context of the main chrome |
| 178 // process. |
| 179 class ImportProcessRunner : public base::win::ObjectWatcher::Delegate { |
| 180 public: |
| 181 // The constructor takes the importer process to watch and then it does a |
| 182 // message loop blocking wait until the process ends. This object now owns |
| 183 // the import_process handle. |
| 184 explicit ImportProcessRunner(base::ProcessHandle import_process) |
| 185 : import_process_(import_process), |
| 186 exit_code_(content::RESULT_CODE_NORMAL_EXIT) { |
| 187 watcher_.StartWatching(import_process, this); |
| 188 MessageLoop::current()->Run(); |
| 189 } |
| 190 virtual ~ImportProcessRunner() { |
| 191 ::CloseHandle(import_process_); |
| 192 } |
| 193 // Returns the child process exit code. There are 2 expected values: |
| 194 // NORMAL_EXIT, or IMPORTER_HUNG. |
| 195 int exit_code() const { return exit_code_; } |
| 196 |
| 197 // The child process has terminated. Find the exit code and quit the loop. |
| 198 virtual void OnObjectSignaled(HANDLE object) OVERRIDE { |
| 199 DCHECK(object == import_process_); |
| 200 if (!::GetExitCodeProcess(import_process_, &exit_code_)) { |
| 201 NOTREACHED(); |
| 202 } |
| 203 MessageLoop::current()->Quit(); |
| 204 } |
| 205 |
| 206 private: |
| 207 base::win::ObjectWatcher watcher_; |
| 208 base::ProcessHandle import_process_; |
| 209 DWORD exit_code_; |
| 210 }; |
| 211 |
| 212 // Check every 3 seconds if the importer UI has hung. |
| 213 const int kPollHangFrequency = 3000; |
| 214 |
| 215 // This class specializes on finding hung 'owned' windows. Unfortunately, the |
| 216 // HungWindowDetector class cannot be used here because it assumes child |
| 217 // windows and not owned top-level windows. |
| 218 // This code is executed in the context of the main browser process and will |
| 219 // terminate the importer process if it is hung. |
| 220 class HungImporterMonitor : public WorkerThreadTicker::Callback { |
| 221 public: |
| 222 // The ctor takes the owner popup window and the process handle of the |
| 223 // process to kill in case the popup or its owned active popup become |
| 224 // unresponsive. |
| 225 HungImporterMonitor(HWND owner_window, base::ProcessHandle import_process) |
| 226 : owner_window_(owner_window), |
| 227 import_process_(import_process), |
| 228 ticker_(kPollHangFrequency) { |
| 229 ticker_.RegisterTickHandler(this); |
| 230 ticker_.Start(); |
| 231 } |
| 232 virtual ~HungImporterMonitor() { |
| 233 ticker_.Stop(); |
| 234 ticker_.UnregisterTickHandler(this); |
| 235 } |
| 236 |
| 237 private: |
| 238 virtual void OnTick() OVERRIDE { |
| 239 if (!import_process_) |
| 240 return; |
| 241 // We find the top active popup that we own, this will be either the |
| 242 // owner_window_ itself or the dialog window of the other process. In |
| 243 // both cases it is worth hung testing because both windows share the |
| 244 // same message queue and at some point the other window could be gone |
| 245 // while the other process still not pumping messages. |
| 246 HWND active_window = ::GetLastActivePopup(owner_window_); |
| 247 if (::IsHungAppWindow(active_window) || ::IsHungAppWindow(owner_window_)) { |
| 248 ::TerminateProcess(import_process_, chrome::RESULT_CODE_IMPORTER_HUNG); |
| 249 import_process_ = NULL; |
| 250 } |
| 251 } |
| 252 |
| 253 HWND owner_window_; |
| 254 base::ProcessHandle import_process_; |
| 255 WorkerThreadTicker ticker_; |
| 256 DISALLOW_COPY_AND_ASSIGN(HungImporterMonitor); |
| 257 }; |
| 258 |
| 259 std::string EncodeImportParams(int importer_type, |
| 260 int options, |
| 261 bool skip_first_run_ui) { |
| 262 return base::StringPrintf("%d@%d@%d", importer_type, options, |
| 263 skip_first_run_ui ? 1 : 0); |
| 264 } |
| 265 |
| 266 bool DecodeImportParams(const std::string& encoded, |
| 267 int* importer_type, |
| 268 int* options, |
| 269 bool* skip_first_run_ui) { |
| 270 std::vector<std::string> parts; |
| 271 base::SplitString(encoded, '@', &parts); |
| 272 int skip_first_run_ui_int; |
| 273 if ((parts.size() != 3) || !base::StringToInt(parts[0], importer_type) || |
| 274 !base::StringToInt(parts[1], options) || |
| 275 !base::StringToInt(parts[2], &skip_first_run_ui_int)) |
| 276 return false; |
| 277 *skip_first_run_ui = !!skip_first_run_ui_int; |
| 278 return true; |
| 279 } |
| 280 |
| 281 #if !defined(USE_AURA) |
| 282 // Imports browser items in this process. The browser and the items to |
| 283 // import are encoded in the command line. |
| 284 int ImportFromBrowser(Profile* profile, |
| 285 const CommandLine& cmdline) { |
| 286 std::string import_info = cmdline.GetSwitchValueASCII(switches::kImport); |
| 287 if (import_info.empty()) { |
| 288 NOTREACHED(); |
| 289 return false; |
| 290 } |
| 291 int importer_type = 0; |
| 292 int items_to_import = 0; |
| 293 bool skip_first_run_ui = false; |
| 294 if (!DecodeImportParams(import_info, &importer_type, &items_to_import, |
| 295 &skip_first_run_ui)) { |
| 296 NOTREACHED(); |
| 297 return false; |
| 298 } |
| 299 |
| 300 // Deletes itself. |
| 301 ImporterHost* importer_host = new ImporterHost; |
| 302 |
| 303 scoped_refptr<ImporterList> importer_list(new ImporterList(NULL)); |
| 304 importer_list->DetectSourceProfilesHack(); |
| 305 |
| 306 // If |skip_first_run_ui|, we run in headless mode. This means that if |
| 307 // there is user action required the import is automatically canceled. |
| 308 if (skip_first_run_ui) |
| 309 importer_host->set_headless(); |
| 310 |
| 311 first_run::internal::ImportEndedObserver observer; |
| 312 importer_host->SetObserver(&observer); |
| 313 importer_host->StartImportSettings( |
| 314 importer_list->GetSourceProfileForImporterType(importer_type), profile, |
| 315 static_cast<uint16>(items_to_import), new ProfileWriter(profile)); |
| 316 // If the import process has not errored out, block on it. |
| 317 if (!observer.ended()) { |
| 318 observer.set_should_quit_message_loop(); |
| 319 MessageLoop::current()->Run(); |
| 320 } |
| 321 // TODO(gab): This method will be go away as part of http://crbug.com/219419/, |
| 322 // so it is fine to hardcode |RESULT_CODE_NORMAL_EXIT| here for now. |
| 323 return content::RESULT_CODE_NORMAL_EXIT; |
| 324 } |
| 325 #endif // !defined(USE_AURA) |
| 326 |
| 327 bool ImportSettingsWin(Profile* profile, |
| 328 int importer_type, |
| 329 int items_to_import, |
| 330 const base::FilePath& import_bookmarks_path, |
| 331 bool skip_first_run_ui) { |
| 332 if (!items_to_import && import_bookmarks_path.empty()) { |
| 333 return true; |
| 334 } |
| 335 |
| 336 const CommandLine& cmdline = *CommandLine::ForCurrentProcess(); |
| 337 base::FilePath chrome_exe(cmdline.GetProgram()); |
| 338 // |chrome_exe| cannot be a relative path as chrome.exe already changed its |
| 339 // CWD in LoadChromeWithDirectory(), making the relative path used on the |
| 340 // command-line invalid. The base name is sufficient given chrome.exe is in |
| 341 // the CWD. |
| 342 if (!chrome_exe.IsAbsolute()) |
| 343 chrome_exe = chrome_exe.BaseName(); |
| 344 CommandLine import_cmd(chrome_exe); |
| 345 |
| 346 const char* kSwitchNames[] = { |
| 347 switches::kUserDataDir, |
| 348 switches::kChromeFrame, |
| 349 switches::kCountry, |
| 350 }; |
| 351 import_cmd.CopySwitchesFrom(cmdline, kSwitchNames, arraysize(kSwitchNames)); |
| 352 |
| 353 // Allow tests to introduce additional switches. |
| 354 import_cmd.AppendArguments(first_run::GetExtraArgumentsForImportProcess(), |
| 355 false); |
| 356 |
| 357 // Since ImportSettings is called before the local state is stored on disk |
| 358 // we pass the language as an argument. GetApplicationLocale checks the |
| 359 // current command line as fallback. |
| 360 import_cmd.AppendSwitchASCII(switches::kLang, |
| 361 g_browser_process->GetApplicationLocale()); |
| 362 |
| 363 if (items_to_import) { |
| 364 import_cmd.AppendSwitchASCII(switches::kImport, |
| 365 EncodeImportParams(importer_type, items_to_import, skip_first_run_ui)); |
| 366 } |
| 367 |
| 368 if (!import_bookmarks_path.empty()) { |
| 369 import_cmd.AppendSwitchPath(switches::kImportFromFile, |
| 370 import_bookmarks_path); |
| 371 } |
| 372 |
| 373 // The importer doesn't need to do any background networking tasks so disable |
| 374 // them. |
| 375 import_cmd.CommandLine::AppendSwitch(switches::kDisableBackgroundNetworking); |
| 376 |
| 377 // Time to launch the process that is going to do the import. |
| 378 base::ProcessHandle import_process; |
| 379 if (!base::LaunchProcess(import_cmd, base::LaunchOptions(), &import_process)) |
| 380 return false; |
| 381 |
| 382 // We block inside the import_runner ctor, pumping messages until the |
| 383 // importer process ends. This can happen either by completing the import |
| 384 // or by hang_monitor killing it. |
| 385 ImportProcessRunner import_runner(import_process); |
| 386 |
| 387 // Import process finished. Reload the prefs, because importer may set |
| 388 // the pref value. |
| 389 if (profile) |
| 390 profile->GetPrefs()->ReloadPersistentPrefs(); |
| 391 |
| 392 return (import_runner.exit_code() == content::RESULT_CODE_NORMAL_EXIT); |
| 393 } |
| 394 |
150 } // namespace | 395 } // namespace |
151 | 396 |
152 namespace first_run { | 397 namespace first_run { |
153 namespace internal { | 398 namespace internal { |
154 | 399 |
155 void DoPostImportPlatformSpecificTasks(Profile* /* profile */) { | 400 void DoPostImportPlatformSpecificTasks(Profile* /* profile */) { |
156 // Trigger the Active Setup command for system-level Chromes to finish | 401 // Trigger the Active Setup command for system-level Chromes to finish |
157 // configuring this user's install (e.g. per-user shortcuts). | 402 // configuring this user's install (e.g. per-user shortcuts). |
158 // Delay the task slightly to give Chrome launch I/O priority while also | 403 // Delay the task slightly to give Chrome launch I/O priority while also |
159 // making sure shortcuts are created promptly to avoid annoying the user by | 404 // making sure shortcuts are created promptly to avoid annoying the user by |
160 // re-creating shortcuts he previously deleted. | 405 // re-creating shortcuts he previously deleted. |
161 static const int64 kTiggerActiveSetupDelaySeconds = 5; | 406 static const int64 kTiggerActiveSetupDelaySeconds = 5; |
162 base::FilePath chrome_exe; | 407 base::FilePath chrome_exe; |
163 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { | 408 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { |
164 NOTREACHED(); | 409 NOTREACHED(); |
165 } else if (!InstallUtil::IsPerUserInstall(chrome_exe.value().c_str())) { | 410 } else if (!InstallUtil::IsPerUserInstall(chrome_exe.value().c_str())) { |
166 content::BrowserThread::GetBlockingPool()->PostDelayedTask( | 411 content::BrowserThread::GetBlockingPool()->PostDelayedTask( |
167 FROM_HERE, | 412 FROM_HERE, |
168 base::Bind(&InstallUtil::TriggerActiveSetupCommand), | 413 base::Bind(&InstallUtil::TriggerActiveSetupCommand), |
169 base::TimeDelta::FromSeconds(kTiggerActiveSetupDelaySeconds)); | 414 base::TimeDelta::FromSeconds(kTiggerActiveSetupDelaySeconds)); |
170 } | 415 } |
171 } | 416 } |
172 | 417 |
| 418 bool ImportSettings(Profile* profile, |
| 419 ImporterHost* importer_host, |
| 420 scoped_refptr<ImporterList> importer_list, |
| 421 int items_to_import) { |
| 422 return ImportSettingsWin( |
| 423 profile, |
| 424 importer_list->GetSourceProfileAt(0).importer_type, |
| 425 items_to_import, |
| 426 base::FilePath(), |
| 427 false); |
| 428 } |
| 429 |
173 bool GetFirstRunSentinelFilePath(base::FilePath* path) { | 430 bool GetFirstRunSentinelFilePath(base::FilePath* path) { |
174 return GetSentinelFilePath(chrome::kFirstRunSentinel, path); | 431 return GetSentinelFilePath(chrome::kFirstRunSentinel, path); |
175 } | 432 } |
176 | 433 |
| 434 void SetImportPreferencesAndLaunchImport( |
| 435 MasterPrefs* out_prefs, |
| 436 installer::MasterPreferences* install_prefs) { |
| 437 std::string import_bookmarks_path; |
| 438 install_prefs->GetString( |
| 439 installer::master_preferences::kDistroImportBookmarksFromFilePref, |
| 440 &import_bookmarks_path); |
| 441 |
| 442 if (!IsOrganicFirstRun()) { |
| 443 // If search engines aren't explicitly imported, don't import. |
| 444 if (!(out_prefs->do_import_items & importer::SEARCH_ENGINES)) { |
| 445 out_prefs->dont_import_items |= importer::SEARCH_ENGINES; |
| 446 } |
| 447 // If home page isn't explicitly imported, don't import. |
| 448 if (!(out_prefs->do_import_items & importer::HOME_PAGE)) { |
| 449 out_prefs->dont_import_items |= importer::HOME_PAGE; |
| 450 } |
| 451 // If history isn't explicitly forbidden, do import. |
| 452 if (!(out_prefs->dont_import_items & importer::HISTORY)) { |
| 453 out_prefs->do_import_items |= importer::HISTORY; |
| 454 } |
| 455 } |
| 456 |
| 457 if (out_prefs->do_import_items || !import_bookmarks_path.empty()) { |
| 458 // There is something to import from the default browser. This launches |
| 459 // the importer process and blocks until done or until it fails. |
| 460 scoped_refptr<ImporterList> importer_list(new ImporterList(NULL)); |
| 461 importer_list->DetectSourceProfilesHack(); |
| 462 if (!ImportSettingsWin( |
| 463 NULL, importer_list->GetSourceProfileAt(0).importer_type, |
| 464 out_prefs->do_import_items, base::FilePath::FromWStringHack(UTF8ToWide( |
| 465 import_bookmarks_path)), true)) { |
| 466 LOG(WARNING) << "silent import failed"; |
| 467 } |
| 468 } |
| 469 } |
| 470 |
177 bool ShowPostInstallEULAIfNeeded(installer::MasterPreferences* install_prefs) { | 471 bool ShowPostInstallEULAIfNeeded(installer::MasterPreferences* install_prefs) { |
178 if (IsEULANotAccepted(install_prefs)) { | 472 if (IsEULANotAccepted(install_prefs)) { |
179 // Show the post-installation EULA. This is done by setup.exe and the | 473 // Show the post-installation EULA. This is done by setup.exe and the |
180 // result determines if we continue or not. We wait here until the user | 474 // result determines if we continue or not. We wait here until the user |
181 // dismisses the dialog. | 475 // dismisses the dialog. |
182 | 476 |
183 // The actual eula text is in a resource in chrome. We extract it to | 477 // The actual eula text is in a resource in chrome. We extract it to |
184 // a text file so setup.exe can use it as an inner frame. | 478 // a text file so setup.exe can use it as an inner frame. |
185 base::FilePath inner_html; | 479 base::FilePath inner_html; |
186 if (WriteEULAtoTempFile(&inner_html)) { | 480 if (WriteEULAtoTempFile(&inner_html)) { |
(...skipping 21 matching lines...) Expand all Loading... |
208 base::FilePath MasterPrefsPath() { | 502 base::FilePath MasterPrefsPath() { |
209 // The standard location of the master prefs is next to the chrome binary. | 503 // The standard location of the master prefs is next to the chrome binary. |
210 base::FilePath master_prefs; | 504 base::FilePath master_prefs; |
211 if (!PathService::Get(base::DIR_EXE, &master_prefs)) | 505 if (!PathService::Get(base::DIR_EXE, &master_prefs)) |
212 return base::FilePath(); | 506 return base::FilePath(); |
213 return master_prefs.AppendASCII(installer::kDefaultMasterPrefs); | 507 return master_prefs.AppendASCII(installer::kDefaultMasterPrefs); |
214 } | 508 } |
215 | 509 |
216 } // namespace internal | 510 } // namespace internal |
217 } // namespace first_run | 511 } // namespace first_run |
| 512 |
| 513 namespace first_run { |
| 514 |
| 515 int ImportNow(Profile* profile, const CommandLine& cmdline) { |
| 516 int return_code = internal::ImportBookmarkFromFileIfNeeded(profile, cmdline); |
| 517 #if !defined(USE_AURA) |
| 518 if (cmdline.HasSwitch(switches::kImport)) { |
| 519 return_code = ImportFromBrowser(profile, cmdline); |
| 520 } |
| 521 #endif |
| 522 return return_code; |
| 523 } |
| 524 |
| 525 } // namespace first_run |
OLD | NEW |