| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/first_run/first_run.h" |
| |
| #include <shellapi.h> |
| #include <shlobj.h> |
| #include <windows.h> |
| |
| #include "base/callback.h" |
| #include "base/environment.h" |
| #include "base/file_path.h" |
| #include "base/file_util.h" |
| #include "base/path_service.h" |
| #include "base/process_util.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_split.h" |
| #include "base/stringprintf.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "base/time.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/win/metro.h" |
| #include "base/win/object_watcher.h" |
| #include "base/win/windows_version.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/first_run/first_run_import_observer.h" |
| #include "chrome/browser/first_run/first_run_internal.h" |
| #include "chrome/browser/importer/importer_host.h" |
| #include "chrome/browser/importer/importer_list.h" |
| #include "chrome/browser/importer/importer_progress_dialog.h" |
| #include "chrome/browser/prefs/pref_service.h" |
| #include "chrome/browser/process_singleton.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/shell_integration.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_result_codes.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/worker_thread_ticker.h" |
| #include "chrome/installer/util/browser_distribution.h" |
| #include "chrome/installer/util/google_update_settings.h" |
| #include "chrome/installer/util/install_util.h" |
| #include "chrome/installer/util/master_preferences.h" |
| #include "chrome/installer/util/master_preferences_constants.h" |
| #include "chrome/installer/util/shell_util.h" |
| #include "chrome/installer/util/util_constants.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/user_metrics.h" |
| #include "google_update/google_update_idl.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "grit/locale_settings.h" |
| #include "grit/theme_resources.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/layout.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/base/win/shell.h" |
| |
| namespace { |
| |
| // Launches the setup exe with the given parameter/value on the command-line. |
| // For non-metro Windows, it waits for its termination, returns its exit code |
| // in |*ret_code|, and returns true if the exit code is valid. |
| // For metro Windows, it launches setup via ShellExecuteEx and returns in order |
| // to bounce the user back to the desktop, then returns immediately. |
| bool LaunchSetupForEula(const FilePath::StringType& value, int* ret_code) { |
| FilePath exe_dir; |
| if (!PathService::Get(base::DIR_MODULE, &exe_dir)) |
| return false; |
| exe_dir = exe_dir.Append(installer::kInstallerDir); |
| FilePath exe_path = exe_dir.Append(installer::kSetupExe); |
| base::ProcessHandle ph; |
| |
| CommandLine cl(CommandLine::NO_PROGRAM); |
| cl.AppendSwitchNative(installer::switches::kShowEula, value); |
| |
| CommandLine* browser_command_line = CommandLine::ForCurrentProcess(); |
| if (browser_command_line->HasSwitch(switches::kChromeFrame)) { |
| cl.AppendSwitch(switches::kChromeFrame); |
| } |
| |
| if (base::win::IsMetroProcess()) { |
| cl.AppendSwitch(installer::switches::kShowEulaForMetro); |
| |
| // This obscure use of the 'log usage' mask for windows 8 is documented here |
| // http://go.microsoft.com/fwlink/?LinkID=243079. It causes the desktop |
| // process to receive focus. Pass SEE_MASK_FLAG_NO_UI to avoid hangs if an |
| // error occurs since the UI can't be shown from a metro process. |
| ui::win::OpenAnyViaShell(exe_path.value(), |
| exe_dir.value(), |
| cl.GetCommandLineString(), |
| SEE_MASK_FLAG_LOG_USAGE | SEE_MASK_FLAG_NO_UI); |
| return false; |
| } else { |
| base::LaunchOptions launch_options; |
| launch_options.wait = true; |
| CommandLine setup_path(exe_path); |
| setup_path.AppendArguments(cl, false); |
| |
| DWORD exit_code = 0; |
| if (!base::LaunchProcess(setup_path, launch_options, &ph) || |
| !::GetExitCodeProcess(ph, &exit_code)) { |
| return false; |
| } |
| |
| *ret_code = exit_code; |
| return true; |
| } |
| } |
| |
| // Populates |path| with the path to |file| in the sentinel directory. This is |
| // the application directory for user-level installs, and the default user data |
| // dir for system-level installs. Returns false on error. |
| bool GetSentinelFilePath(const wchar_t* file, FilePath* path) { |
| FilePath exe_path; |
| if (!PathService::Get(base::DIR_EXE, &exe_path)) |
| return false; |
| if (InstallUtil::IsPerUserInstall(exe_path.value().c_str())) |
| *path = exe_path; |
| else if (!PathService::Get(chrome::DIR_USER_DATA, path)) |
| return false; |
| *path = path->Append(file); |
| return true; |
| } |
| |
| bool GetEULASentinelFilePath(FilePath* path) { |
| return GetSentinelFilePath(installer::kEULASentinelFile, path); |
| } |
| |
| // Returns true if the EULA is required but has not been accepted by this user. |
| // The EULA is considered having been accepted if the user has gotten past |
| // first run in the "other" environment (desktop or metro). |
| bool IsEULANotAccepted(installer::MasterPreferences* install_prefs) { |
| bool value = false; |
| if (install_prefs->GetBool(installer::master_preferences::kRequireEula, |
| &value) && value) { |
| FilePath eula_sentinel; |
| // Be conservative and show the EULA if the path to the sentinel can't be |
| // determined. |
| if (!GetEULASentinelFilePath(&eula_sentinel) || |
| !file_util::PathExists(eula_sentinel)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Writes the EULA to a temporary file, returned in |*eula_path|, and returns |
| // true if successful. |
| bool WriteEULAtoTempFile(FilePath* eula_path) { |
| std::string terms = l10n_util::GetStringUTF8(IDS_TERMS_HTML); |
| if (terms.empty()) |
| return false; |
| FILE *file = file_util::CreateAndOpenTemporaryFile(eula_path); |
| if (!file) |
| return false; |
| bool good = fwrite(terms.data(), terms.size(), 1, file) == 1; |
| fclose(file); |
| return good; |
| } |
| |
| // Creates the sentinel indicating that the EULA was required and has been |
| // accepted. |
| bool CreateEULASentinel() { |
| FilePath eula_sentinel; |
| if (!GetEULASentinelFilePath(&eula_sentinel)) |
| return false; |
| |
| return (file_util::CreateDirectory(eula_sentinel.DirName()) && |
| file_util::WriteFile(eula_sentinel, "", 0) != -1); |
| } |
| |
| // This class is used by first_run::ImportSettings to determine when the import |
| // process has ended and what was the result of the operation as reported by |
| // the process exit code. This class executes in the context of the main chrome |
| // process. |
| class ImportProcessRunner : public base::win::ObjectWatcher::Delegate { |
| public: |
| // The constructor takes the importer process to watch and then it does a |
| // message loop blocking wait until the process ends. This object now owns |
| // the import_process handle. |
| explicit ImportProcessRunner(base::ProcessHandle import_process) |
| : import_process_(import_process), |
| exit_code_(content::RESULT_CODE_NORMAL_EXIT) { |
| watcher_.StartWatching(import_process, this); |
| MessageLoop::current()->Run(); |
| } |
| virtual ~ImportProcessRunner() { |
| ::CloseHandle(import_process_); |
| } |
| // Returns the child process exit code. There are 2 expected values: |
| // NORMAL_EXIT, or IMPORTER_HUNG. |
| int exit_code() const { return exit_code_; } |
| |
| // The child process has terminated. Find the exit code and quit the loop. |
| virtual void OnObjectSignaled(HANDLE object) OVERRIDE { |
| DCHECK(object == import_process_); |
| if (!::GetExitCodeProcess(import_process_, &exit_code_)) { |
| NOTREACHED(); |
| } |
| MessageLoop::current()->Quit(); |
| } |
| |
| private: |
| base::win::ObjectWatcher watcher_; |
| base::ProcessHandle import_process_; |
| DWORD exit_code_; |
| }; |
| |
| // Check every 3 seconds if the importer UI has hung. |
| const int kPollHangFrequency = 3000; |
| |
| // This class specializes on finding hung 'owned' windows. Unfortunately, the |
| // HungWindowDetector class cannot be used here because it assumes child |
| // windows and not owned top-level windows. |
| // This code is executed in the context of the main browser process and will |
| // terminate the importer process if it is hung. |
| class HungImporterMonitor : public WorkerThreadTicker::Callback { |
| public: |
| // The ctor takes the owner popup window and the process handle of the |
| // process to kill in case the popup or its owned active popup become |
| // unresponsive. |
| HungImporterMonitor(HWND owner_window, base::ProcessHandle import_process) |
| : owner_window_(owner_window), |
| import_process_(import_process), |
| ticker_(kPollHangFrequency) { |
| ticker_.RegisterTickHandler(this); |
| ticker_.Start(); |
| } |
| virtual ~HungImporterMonitor() { |
| ticker_.Stop(); |
| ticker_.UnregisterTickHandler(this); |
| } |
| |
| private: |
| virtual void OnTick() OVERRIDE { |
| if (!import_process_) |
| return; |
| // We find the top active popup that we own, this will be either the |
| // owner_window_ itself or the dialog window of the other process. In |
| // both cases it is worth hung testing because both windows share the |
| // same message queue and at some point the other window could be gone |
| // while the other process still not pumping messages. |
| HWND active_window = ::GetLastActivePopup(owner_window_); |
| if (::IsHungAppWindow(active_window) || ::IsHungAppWindow(owner_window_)) { |
| ::TerminateProcess(import_process_, chrome::RESULT_CODE_IMPORTER_HUNG); |
| import_process_ = NULL; |
| } |
| } |
| |
| HWND owner_window_; |
| base::ProcessHandle import_process_; |
| WorkerThreadTicker ticker_; |
| DISALLOW_COPY_AND_ASSIGN(HungImporterMonitor); |
| }; |
| |
| std::string EncodeImportParams(int importer_type, |
| int options, |
| bool skip_first_run_ui) { |
| return base::StringPrintf("%d@%d@%d", importer_type, options, |
| skip_first_run_ui ? 1 : 0); |
| } |
| |
| bool DecodeImportParams(const std::string& encoded, |
| int* importer_type, |
| int* options, |
| bool* skip_first_run_ui) { |
| std::vector<std::string> parts; |
| base::SplitString(encoded, '@', &parts); |
| int skip_first_run_ui_int; |
| if ((parts.size() != 3) || !base::StringToInt(parts[0], importer_type) || |
| !base::StringToInt(parts[1], options) || |
| !base::StringToInt(parts[2], &skip_first_run_ui_int)) |
| return false; |
| *skip_first_run_ui = !!skip_first_run_ui_int; |
| return true; |
| } |
| |
| #if !defined(USE_AURA) |
| // Imports browser items in this process. The browser and the items to |
| // import are encoded in the command line. |
| int ImportFromBrowser(Profile* profile, |
| const CommandLine& cmdline) { |
| std::string import_info = cmdline.GetSwitchValueASCII(switches::kImport); |
| if (import_info.empty()) { |
| NOTREACHED(); |
| return false; |
| } |
| int importer_type = 0; |
| int items_to_import = 0; |
| bool skip_first_run_ui = false; |
| if (!DecodeImportParams(import_info, &importer_type, &items_to_import, |
| &skip_first_run_ui)) { |
| NOTREACHED(); |
| return false; |
| } |
| scoped_refptr<ImporterHost> importer_host(new ImporterHost); |
| FirstRunImportObserver importer_observer; |
| |
| scoped_refptr<ImporterList> importer_list(new ImporterList(NULL)); |
| importer_list->DetectSourceProfilesHack(); |
| |
| // If |skip_first_run_ui|, we run in headless mode. This means that if |
| // there is user action required the import is automatically canceled. |
| if (skip_first_run_ui) |
| importer_host->set_headless(); |
| |
| importer::ShowImportProgressDialog(static_cast<uint16>(items_to_import), |
| importer_host, &importer_observer, |
| importer_list->GetSourceProfileForImporterType(importer_type), profile, |
| true); |
| importer_observer.RunLoop(); |
| return importer_observer.import_result(); |
| } |
| #endif // !defined(USE_AURA) |
| |
| bool ImportSettingsWin(Profile* profile, |
| int importer_type, |
| int items_to_import, |
| const FilePath& import_bookmarks_path, |
| bool skip_first_run_ui) { |
| if (!items_to_import && import_bookmarks_path.empty()) { |
| return true; |
| } |
| |
| const CommandLine& cmdline = *CommandLine::ForCurrentProcess(); |
| CommandLine import_cmd(cmdline.GetProgram()); |
| |
| const char* kSwitchNames[] = { |
| switches::kUserDataDir, |
| switches::kChromeFrame, |
| switches::kCountry, |
| }; |
| import_cmd.CopySwitchesFrom(cmdline, kSwitchNames, arraysize(kSwitchNames)); |
| |
| // Allow tests to introduce additional switches. |
| import_cmd.AppendArguments(first_run::GetExtraArgumentsForImportProcess(), |
| false); |
| |
| // Since ImportSettings is called before the local state is stored on disk |
| // we pass the language as an argument. GetApplicationLocale checks the |
| // current command line as fallback. |
| import_cmd.AppendSwitchASCII(switches::kLang, |
| g_browser_process->GetApplicationLocale()); |
| |
| if (items_to_import) { |
| import_cmd.AppendSwitchASCII(switches::kImport, |
| EncodeImportParams(importer_type, items_to_import, skip_first_run_ui)); |
| } |
| |
| if (!import_bookmarks_path.empty()) { |
| import_cmd.AppendSwitchPath(switches::kImportFromFile, |
| import_bookmarks_path); |
| } |
| |
| // The importer doesn't need to do any background networking tasks so disable |
| // them. |
| import_cmd.CommandLine::AppendSwitch(switches::kDisableBackgroundNetworking); |
| |
| // Time to launch the process that is going to do the import. |
| base::ProcessHandle import_process; |
| if (!base::LaunchProcess(import_cmd, base::LaunchOptions(), &import_process)) |
| return false; |
| |
| // We block inside the import_runner ctor, pumping messages until the |
| // importer process ends. This can happen either by completing the import |
| // or by hang_monitor killing it. |
| ImportProcessRunner import_runner(import_process); |
| |
| // Import process finished. Reload the prefs, because importer may set |
| // the pref value. |
| if (profile) |
| profile->GetPrefs()->ReloadPersistentPrefs(); |
| |
| return (import_runner.exit_code() == content::RESULT_CODE_NORMAL_EXIT); |
| } |
| |
| } // namespace |
| |
| namespace first_run { |
| namespace internal { |
| |
| void DoPostImportPlatformSpecificTasks() { |
| // Trigger the Active Setup command for system-level Chromes to finish |
| // configuring this user's install (e.g. per-user shortcuts). |
| // Delay the task slightly to give Chrome launch I/O priority while also |
| // making sure shortcuts are created promptly to avoid annoying the user by |
| // re-creating shortcuts he previously deleted. |
| static const int64 kTiggerActiveSetupDelaySeconds = 5; |
| FilePath chrome_exe; |
| if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { |
| NOTREACHED(); |
| } else if (!InstallUtil::IsPerUserInstall(chrome_exe.value().c_str())) { |
| content::BrowserThread::GetBlockingPool()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&InstallUtil::TriggerActiveSetupCommand), |
| base::TimeDelta::FromSeconds(kTiggerActiveSetupDelaySeconds)); |
| } |
| } |
| |
| bool ImportSettings(Profile* profile, |
| scoped_refptr<ImporterHost> importer_host, |
| scoped_refptr<ImporterList> importer_list, |
| int items_to_import) { |
| return ImportSettingsWin( |
| profile, |
| importer_list->GetSourceProfileAt(0).importer_type, |
| items_to_import, |
| FilePath(), |
| false); |
| } |
| |
| bool GetFirstRunSentinelFilePath(FilePath* path) { |
| return GetSentinelFilePath(chrome::kFirstRunSentinel, path); |
| } |
| |
| void SetImportPreferencesAndLaunchImport( |
| MasterPrefs* out_prefs, |
| installer::MasterPreferences* install_prefs) { |
| std::string import_bookmarks_path; |
| install_prefs->GetString( |
| installer::master_preferences::kDistroImportBookmarksFromFilePref, |
| &import_bookmarks_path); |
| |
| if (!IsOrganicFirstRun()) { |
| // If search engines aren't explicitly imported, don't import. |
| if (!(out_prefs->do_import_items & importer::SEARCH_ENGINES)) { |
| out_prefs->dont_import_items |= importer::SEARCH_ENGINES; |
| } |
| // If home page isn't explicitly imported, don't import. |
| if (!(out_prefs->do_import_items & importer::HOME_PAGE)) { |
| out_prefs->dont_import_items |= importer::HOME_PAGE; |
| } |
| // If history isn't explicitly forbidden, do import. |
| if (!(out_prefs->dont_import_items & importer::HISTORY)) { |
| out_prefs->do_import_items |= importer::HISTORY; |
| } |
| } |
| |
| if (out_prefs->do_import_items || !import_bookmarks_path.empty()) { |
| // There is something to import from the default browser. This launches |
| // the importer process and blocks until done or until it fails. |
| scoped_refptr<ImporterList> importer_list(new ImporterList(NULL)); |
| importer_list->DetectSourceProfilesHack(); |
| if (!ImportSettingsWin(NULL, |
| importer_list->GetSourceProfileAt(0).importer_type, |
| out_prefs->do_import_items, |
| FilePath::FromWStringHack(UTF8ToWide(import_bookmarks_path)), true)) { |
| LOG(WARNING) << "silent import failed"; |
| } |
| } |
| } |
| |
| bool ShowPostInstallEULAIfNeeded(installer::MasterPreferences* install_prefs) { |
| if (IsEULANotAccepted(install_prefs)) { |
| // Show the post-installation EULA. This is done by setup.exe and the |
| // result determines if we continue or not. We wait here until the user |
| // dismisses the dialog. |
| |
| // The actual eula text is in a resource in chrome. We extract it to |
| // a text file so setup.exe can use it as an inner frame. |
| FilePath inner_html; |
| if (WriteEULAtoTempFile(&inner_html)) { |
| int retcode = 0; |
| if (!LaunchSetupForEula(inner_html.value(), &retcode) || |
| (retcode != installer::EULA_ACCEPTED && |
| retcode != installer::EULA_ACCEPTED_OPT_IN)) { |
| LOG(WARNING) << "EULA flow requires fast exit."; |
| return false; |
| } |
| CreateEULASentinel(); |
| |
| if (retcode == installer::EULA_ACCEPTED) { |
| VLOG(1) << "EULA : no collection"; |
| GoogleUpdateSettings::SetCollectStatsConsent(false); |
| } else if (retcode == installer::EULA_ACCEPTED_OPT_IN) { |
| VLOG(1) << "EULA : collection consent"; |
| GoogleUpdateSettings::SetCollectStatsConsent(true); |
| } |
| } |
| } |
| return true; |
| } |
| |
| } // namespace internal |
| } // namespace first_run |
| |
| namespace first_run { |
| |
| int ImportNow(Profile* profile, const CommandLine& cmdline) { |
| int return_code = internal::ImportBookmarkFromFileIfNeeded(profile, cmdline); |
| #if !defined(USE_AURA) |
| if (cmdline.HasSwitch(switches::kImport)) { |
| return_code = ImportFromBrowser(profile, cmdline); |
| } |
| #endif |
| return return_code; |
| } |
| |
| FilePath MasterPrefsPath() { |
| // The standard location of the master prefs is next to the chrome binary. |
| FilePath master_prefs; |
| if (!PathService::Get(base::DIR_EXE, &master_prefs)) |
| return FilePath(); |
| return master_prefs.AppendASCII(installer::kDefaultMasterPrefs); |
| } |
| |
| } // namespace first_run |