// Copyright (c) 2011 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 "base/command_line.h" #include "base/compiler_specific.h" #include "base/file_util.h" #include "base/path_service.h" #include "base/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/first_run/first_run_dialog.h" #include "chrome/browser/first_run/first_run_import_observer.h" #include "chrome/browser/importer/external_process_importer_host.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/importer/importer_progress_observer.h" #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/process_singleton.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/search_engines/template_url_model.h" #include "chrome/browser/shell_integration.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/installer/util/master_preferences.h" #include "chrome/installer/util/master_preferences_constants.h" #include "chrome/installer/util/util_constants.h" #include "googleurl/src/gurl.h" #if defined(OS_WIN) // TODO(port): move more code in back from the first_run_win.cc module. #include "chrome/installer/util/google_update_settings.h" #include "chrome/installer/util/install_util.h" #endif namespace { // The kSentinelFile file absence will tell us it is a first run. const char kSentinelFile[] = "First Run"; FilePath GetDefaultPrefFilePath(bool create_profile_dir, const FilePath& user_data_dir) { FilePath default_pref_dir = ProfileManager::GetDefaultProfileDir(user_data_dir); if (create_profile_dir) { if (!file_util::PathExists(default_pref_dir)) { if (!file_util::CreateDirectory(default_pref_dir)) return FilePath(); } } return ProfileManager::GetProfilePrefsPath(default_pref_dir); } } // namespace // FirstRun ------------------------------------------------------------------- FirstRun::FirstRunState FirstRun::first_run_ = FIRST_RUN_UNKNOWN; FirstRun::MasterPrefs::MasterPrefs() : ping_delay(0), homepage_defined(false), do_import_items(0), dont_import_items(0), run_search_engine_experiment(false), randomize_search_engine_experiment(false), make_chrome_default(false) { } FirstRun::MasterPrefs::~MasterPrefs() {} // TODO(port): Import switches need to be ported to both Mac and Linux. Not all // import switches here are implemented for Linux. None are implemented for Mac // (as this function will not be called on Mac). int FirstRun::ImportNow(Profile* profile, const CommandLine& cmdline) { int return_code = true; if (cmdline.HasSwitch(switches::kImportFromFile)) { // Silently import preset bookmarks from file. // This is an OEM scenario. return_code = ImportFromFile(profile, cmdline); } if (cmdline.HasSwitch(switches::kImport)) { #if defined(OS_WIN) return_code = ImportFromBrowser(profile, cmdline); #else NOTIMPLEMENTED(); #endif } return return_code; } // static bool FirstRun::ProcessMasterPreferences(const FilePath& user_data_dir, MasterPrefs* out_prefs) { DCHECK(!user_data_dir.empty()); // 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 true; master_prefs = master_prefs.AppendASCII(installer::kDefaultMasterPrefs); installer::MasterPreferences prefs(master_prefs); if (!prefs.read_from_file()) return true; out_prefs->new_tabs = prefs.GetFirstRunTabs(); bool value = false; #if defined(OS_WIN) // RLZ is currently a Windows-only phenomenon. When it comes to the Mac/ // Linux, enable it here. if (!prefs.GetInt(installer::master_preferences::kDistroPingDelay, &out_prefs->ping_delay)) { // 90 seconds is the default that we want to use in case master // preferences is missing, corrupt or ping_delay is missing. out_prefs->ping_delay = 90; } if (prefs.GetBool(installer::master_preferences::kRequireEula, &value) && value) { // 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 (!LaunchSetupWithParam(installer::switches::kShowEula, inner_html.value(), &retcode) || (retcode == installer::EULA_REJECTED)) { LOG(WARNING) << "EULA rejected. Fast exit."; ::ExitProcess(1); } 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); } } } #endif if (prefs.GetBool(installer::master_preferences::kAltFirstRunBubble, &value) && value) { FirstRun::SetOEMFirstRunBubblePref(); } FilePath user_prefs = GetDefaultPrefFilePath(true, user_data_dir); if (user_prefs.empty()) return true; // The master prefs are regular prefs so we can just copy the file // to the default place and they just work. if (!file_util::CopyFile(master_prefs, user_prefs)) return true; #if defined(OS_WIN) DictionaryValue* extensions = 0; if (prefs.GetExtensionsBlock(&extensions)) { VLOG(1) << "Extensions block found in master preferences"; DoDelayedInstallExtensions(); } #endif if (prefs.GetBool(installer::master_preferences::kDistroImportSearchPref, &value)) { if (value) { out_prefs->do_import_items |= importer::SEARCH_ENGINES; } else { out_prefs->dont_import_items |= importer::SEARCH_ENGINES; } } // Check to see if search engine logos should be randomized. if (prefs.GetBool( installer::master_preferences:: kSearchEngineExperimentRandomizePref, &value) && value) { out_prefs->randomize_search_engine_experiment = true; } // If we're suppressing the first-run bubble, set that preference now. // Otherwise, wait until the user has completed first run to set it, so the // user is guaranteed to see the bubble iff he or she has completed the first // run process. if (prefs.GetBool( installer::master_preferences::kDistroSuppressFirstRunBubble, &value) && value) FirstRun::SetShowFirstRunBubblePref(false); if (prefs.GetBool( installer::master_preferences::kDistroImportHistoryPref, &value)) { if (value) { out_prefs->do_import_items |= importer::HISTORY; } else { out_prefs->dont_import_items |= importer::HISTORY; } } std::string not_used; out_prefs->homepage_defined = prefs.GetString(prefs::kHomePage, ¬_used); if (prefs.GetBool( installer::master_preferences::kDistroImportHomePagePref, &value)) { if (value) { out_prefs->do_import_items |= importer::HOME_PAGE; } else { out_prefs->dont_import_items |= importer::HOME_PAGE; } } // Bookmarks are never imported unless specifically turned on. if (prefs.GetBool( installer::master_preferences::kDistroImportBookmarksPref, &value) && value) { out_prefs->do_import_items |= importer::FAVORITES; } if (prefs.GetBool( installer::master_preferences::kMakeChromeDefaultForUser, &value) && value) { out_prefs->make_chrome_default = true; } // TODO(mirandac): Refactor skip-first-run-ui process into regular first run // import process. http://crbug.com/49647 // Note we are skipping all other master preferences if skip-first-run-ui // is *not* specified. (That is, we continue only if skipping first run ui.) if (!prefs.GetBool( installer::master_preferences::kDistroSkipFirstRunPref, &value) || !value) { return true; } #if !defined(OS_WIN) // From here on we won't show first run so we need to do the work to show the // bubble anyway, unless it's already been explicitly suppressed. FirstRun::SetShowFirstRunBubblePref(true); #endif // We need to be able to create the first run sentinel or else we cannot // proceed because ImportSettings will launch the importer process which // would end up here if the sentinel is not present. if (!FirstRun::CreateSentinel()) return false; if (prefs.GetBool(installer::master_preferences::kDistroShowWelcomePage, &value) && value) { FirstRun::SetShowWelcomePagePref(); } std::string import_bookmarks_path; prefs.GetString( installer::master_preferences::kDistroImportBookmarksFromFilePref, &import_bookmarks_path); #if defined(OS_WIN) 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); importer_list->DetectSourceProfilesHack(); if (!FirstRun::ImportSettings(NULL, importer_list->GetSourceProfileAt(0).importer_type, out_prefs->do_import_items, FilePath::FromWStringHack(UTF8ToWide(import_bookmarks_path)), true, NULL)) { LOG(WARNING) << "silent import failed"; } } #else if (!import_bookmarks_path.empty()) { // There are bookmarks to import from a file. FilePath path = FilePath::FromWStringHack(UTF8ToWide( import_bookmarks_path)); if (!FirstRun::ImportBookmarks(path)) { LOG(WARNING) << "silent bookmark import failed"; } } #endif // Even on the first run we only allow for the user choice to take effect if // no policy has been set by the admin. if (!g_browser_process->local_state()->IsManagedPreference( prefs::kDefaultBrowserSettingEnabled)) { if (prefs.GetBool( installer::master_preferences::kMakeChromeDefaultForUser, &value) && value) { ShellIntegration::SetAsDefaultBrowser(); } } else { if (g_browser_process->local_state()->GetBoolean( prefs::kDefaultBrowserSettingEnabled)) { ShellIntegration::SetAsDefaultBrowser(); } } return false; } // static bool FirstRun::IsChromeFirstRun() { if (first_run_ != FIRST_RUN_UNKNOWN) return first_run_ == FIRST_RUN_TRUE; FilePath first_run_sentinel; if (!GetFirstRunSentinelFilePath(&first_run_sentinel) || file_util::PathExists(first_run_sentinel)) { first_run_ = FIRST_RUN_FALSE; return false; } first_run_ = FIRST_RUN_TRUE; return true; } // static bool FirstRun::RemoveSentinel() { FilePath first_run_sentinel; if (!GetFirstRunSentinelFilePath(&first_run_sentinel)) return false; return file_util::Delete(first_run_sentinel, false); } // static bool FirstRun::CreateSentinel() { FilePath first_run_sentinel; if (!GetFirstRunSentinelFilePath(&first_run_sentinel)) return false; return file_util::WriteFile(first_run_sentinel, "", 0) != -1; } // static bool FirstRun::SetShowFirstRunBubblePref(bool show_bubble) { PrefService* local_state = g_browser_process->local_state(); if (!local_state) return false; if (!local_state->FindPreference(prefs::kShouldShowFirstRunBubble)) { local_state->RegisterBooleanPref(prefs::kShouldShowFirstRunBubble, false); local_state->SetBoolean(prefs::kShouldShowFirstRunBubble, show_bubble); } return true; } // static bool FirstRun::SetShowWelcomePagePref() { PrefService* local_state = g_browser_process->local_state(); if (!local_state) return false; if (!local_state->FindPreference(prefs::kShouldShowWelcomePage)) { local_state->RegisterBooleanPref(prefs::kShouldShowWelcomePage, false); local_state->SetBoolean(prefs::kShouldShowWelcomePage, true); } return true; } // static bool FirstRun::SetPersonalDataManagerFirstRunPref() { PrefService* local_state = g_browser_process->local_state(); if (!local_state) return false; if (!local_state->FindPreference( prefs::kAutofillPersonalDataManagerFirstRun)) { local_state->RegisterBooleanPref( prefs::kAutofillPersonalDataManagerFirstRun, false); local_state->SetBoolean(prefs::kAutofillPersonalDataManagerFirstRun, true); } return true; } // static bool FirstRun::SearchEngineSelectorDisallowed() { // For now, the only case in which the search engine dialog should never be // shown is if the locale is Russia. std::string locale = g_browser_process->GetApplicationLocale(); return (locale == "ru"); } // static bool FirstRun::SetOEMFirstRunBubblePref() { PrefService* local_state = g_browser_process->local_state(); if (!local_state) return false; if (!local_state->FindPreference(prefs::kShouldUseOEMFirstRunBubble)) { local_state->RegisterBooleanPref(prefs::kShouldUseOEMFirstRunBubble, false); local_state->SetBoolean(prefs::kShouldUseOEMFirstRunBubble, true); } return true; } // static bool FirstRun::SetMinimalFirstRunBubblePref() { PrefService* local_state = g_browser_process->local_state(); if (!local_state) return false; if (!local_state->FindPreference(prefs::kShouldUseMinimalFirstRunBubble)) { local_state->RegisterBooleanPref(prefs::kShouldUseMinimalFirstRunBubble, false); local_state->SetBoolean(prefs::kShouldUseMinimalFirstRunBubble, true); } return true; } // static int FirstRun::ImportFromFile(Profile* profile, const CommandLine& cmdline) { FilePath file_path = cmdline.GetSwitchValuePath(switches::kImportFromFile); if (file_path.empty()) { NOTREACHED(); return false; } scoped_refptr<ImporterHost> importer_host(new ImporterHost); importer_host->set_headless(); importer::SourceProfile source_profile; source_profile.importer_type = importer::BOOKMARKS_HTML; source_profile.source_path = file_path; FirstRunImportObserver importer_observer; importer::ShowImportProgressDialog(NULL, importer::FAVORITES, importer_host, &importer_observer, source_profile, profile, true); importer_observer.RunLoop(); return importer_observer.import_result(); } // static bool FirstRun::GetFirstRunSentinelFilePath(FilePath* path) { FilePath first_run_sentinel; #if defined(OS_WIN) FilePath exe_path; if (!PathService::Get(base::DIR_EXE, &exe_path)) return false; if (InstallUtil::IsPerUserInstall(exe_path.value().c_str())) { first_run_sentinel = exe_path; } else { if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel)) return false; } #else if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel)) return false; #endif *path = first_run_sentinel.AppendASCII(kSentinelFile); return true; } // static void FirstRun::AutoImport( Profile* profile, bool homepage_defined, int import_items, int dont_import_items, bool search_engine_experiment, bool randomize_search_engine_experiment, bool make_chrome_default, ProcessSingleton* process_singleton) { // We need to avoid dispatching new tabs when we are importing because // that will lead to data corruption or a crash. Because there is no UI for // the import process, we pass NULL as the window to bring to the foreground // when a CopyData message comes in; this causes the message to be silently // discarded, which is the correct behavior during the import process. process_singleton->Lock(NULL); PlatformSetup(); FilePath local_state_path; PathService::Get(chrome::FILE_LOCAL_STATE, &local_state_path); bool local_state_file_exists = file_util::PathExists(local_state_path); scoped_refptr<ImporterHost> importer_host; // TODO(csilv,mirandac): Out-of-process import has only been qualified on // MacOS X, so we will only use it on that platform since it is required. // Remove this conditional logic once oop import is qualified for // Linux/Windows. http://crbug.com/22142 #if defined(OS_MACOSX) importer_host = new ExternalProcessImporterHost; #else importer_host = new ImporterHost; #endif scoped_refptr<ImporterList> importer_list(new ImporterList); importer_list->DetectSourceProfilesHack(); // Do import if there is an available profile for us to import. if (importer_list->count() > 0) { // Don't show the warning dialog if import fails. importer_host->set_headless(); int items = 0; // History is always imported unless turned off in master_preferences. if (!(dont_import_items & importer::HISTORY)) items = items | importer::HISTORY; // Home page is imported in organic builds only unless turned off or // defined in master_preferences. if (IsOrganicFirstRun()) { if (!(dont_import_items & importer::HOME_PAGE) && !homepage_defined) items = items | importer::HOME_PAGE; } else { if (import_items & importer::HOME_PAGE) items = items | importer::HOME_PAGE; } // Search engines are only imported in certain builds unless overridden // in master_preferences. Search engines are not imported automatically // if the user already has a user preferences directory. if (IsOrganicFirstRun()) { if (!(dont_import_items & importer::SEARCH_ENGINES) && !local_state_file_exists) { items = items | importer::SEARCH_ENGINES; } } else if (import_items & importer::SEARCH_ENGINES) { items = items | importer::SEARCH_ENGINES; } // Bookmarks are never imported, unless turned on in master_preferences. if (import_items & importer::FAVORITES) items = items | importer::FAVORITES; ImportSettings(profile, importer_host, importer_list, items); } UserMetrics::RecordAction(UserMetricsAction("FirstRunDef_Accept")); // Launch the search engine dialog only for certain builds, and only if the // user has not already set preferences. if (IsOrganicFirstRun() && !local_state_file_exists) { // The home page string may be set in the preferences, but the user should // initially use Chrome with the NTP as home page in organic builds. profile->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage, true); first_run::ShowFirstRunDialog(profile, randomize_search_engine_experiment); } if (make_chrome_default) ShellIntegration::SetAsDefaultBrowser(); // Don't display the minimal bubble if there is no default search provider. TemplateURLModel* search_engines_model = profile->GetTemplateURLModel(); if (search_engines_model && search_engines_model->GetDefaultSearchProvider()) { FirstRun::SetShowFirstRunBubblePref(true); // Set the first run bubble to minimal. FirstRun::SetMinimalFirstRunBubblePref(); } FirstRun::SetShowWelcomePagePref(); FirstRun::SetPersonalDataManagerFirstRunPref(); process_singleton->Unlock(); FirstRun::CreateSentinel(); } #if defined(OS_POSIX) namespace { // This class acts as an observer for the ImporterProgressObserver::ImportEnded // callback. When the import process is started, certain errors may cause // ImportEnded() to be called synchronously, but the typical case is that // ImportEnded() is called asynchronously. Thus we have to handle both cases. class ImportEndedObserver : public importer::ImporterProgressObserver { public: ImportEndedObserver() : ended_(false), should_quit_message_loop_(false) {} virtual ~ImportEndedObserver() {} // importer::ImporterProgressObserver: virtual void ImportStarted() OVERRIDE {} virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE {} virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE {} virtual void ImportEnded() OVERRIDE { ended_ = true; if (should_quit_message_loop_) MessageLoop::current()->Quit(); } void set_should_quit_message_loop() { should_quit_message_loop_ = true; } bool ended() { return ended_; } private: // Set if the import has ended. bool ended_; // Set by the client (via set_should_quit_message_loop) if, when the import // ends, this class should quit the message loop. bool should_quit_message_loop_; }; } // namespace // static bool FirstRun::ImportSettings(Profile* profile, scoped_refptr<ImporterHost> importer_host, scoped_refptr<ImporterList> importer_list, int items_to_import) { const importer::SourceProfile& source_profile = importer_list->GetSourceProfileAt(0); // Ensure that importers aren't requested to import items that they do not // support. items_to_import &= source_profile.services_supported; scoped_ptr<ImportEndedObserver> observer(new ImportEndedObserver); importer_host->SetObserver(observer.get()); importer_host->StartImportSettings(source_profile, profile, items_to_import, new ProfileWriter(profile), true); // If the import process has not errored out, block on it. if (!observer->ended()) { observer->set_should_quit_message_loop(); MessageLoop::current()->Run(); } // Unfortunately there's no success/fail signal in ImporterHost. return true; } #endif // OS_POSIX