blob: 8bf2f13c5d0c156334264feabf0c3776e51c8aeb [file] [log] [blame]
// 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 <algorithm>
#include <memory>
#include <utility>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/updater/extension_updater.h"
#include "chrome/browser/first_run/first_run_internal.h"
#include "chrome/browser/google/google_brand.h"
#include "chrome/browser/importer/external_process_importer_host.h"
#include "chrome/browser/importer/importer_list.h"
#include "chrome/browser/importer/importer_progress_observer.h"
#include "chrome/browser/importer/importer_uma.h"
#include "chrome/browser/importer/profile_writer.h"
#include "chrome/browser/prefs/chrome_pref_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/global_error/global_error_service.h"
#include "chrome/browser/ui/global_error/global_error_service_factory.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/installer/util/master_preferences.h"
#include "chrome/installer/util/master_preferences_constants.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/one_shot_event.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "url/gurl.h"
namespace content {
class BrowserContext;
}
using base::UserMetricsAction;
namespace {
// A bitfield formed from values in AutoImportState to record the state of
// AutoImport. This is used in testing to verify import startup actions that
// occur before an observer can be registered in the test.
uint16_t g_auto_import_state = first_run::AUTO_IMPORT_NONE;
// Flags for functions of similar name.
bool g_should_show_welcome_page = false;
bool g_should_do_autofill_personal_data_manager_first_run = false;
// Indicates whether this is first run. Populated when IsChromeFirstRun
// is invoked, then used as a cache on subsequent calls.
first_run::internal::FirstRunState g_first_run =
first_run::internal::FIRST_RUN_UNKNOWN;
// Cached first run sentinel creation time.
// Used to avoid excess file operations.
base::Time g_cached_sentinel_creation_time;
// 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) {}
~ImportEndedObserver() override {}
// importer::ImporterProgressObserver:
void ImportStarted() override {}
void ImportItemStarted(importer::ImportItem item) override {}
void ImportItemEnded(importer::ImportItem item) override {}
void ImportEnded() override {
ended_ = true;
if (!callback_for_import_end_.is_null())
callback_for_import_end_.Run();
}
void set_callback_for_import_end(const base::Closure& callback) {
callback_for_import_end_ = callback;
}
bool ended() const {
return ended_;
}
private:
// Set if the import has ended.
bool ended_;
base::Closure callback_for_import_end_;
DISALLOW_COPY_AND_ASSIGN(ImportEndedObserver);
};
// Helper class that performs delayed first-run tasks that need more of the
// chrome infrastructure to be up and running before they can be attempted.
class FirstRunDelayedTasks : public content::NotificationObserver {
public:
FirstRunDelayedTasks() : weak_ptr_factory_(this) {
registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED,
content::NotificationService::AllSources());
}
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
switch (type) {
case chrome::NOTIFICATION_PROFILE_CREATED: {
content::BrowserContext* context =
content::Source<Profile>(source).ptr();
extensions::ExtensionSystem::Get(context)->ready().Post(
FROM_HERE, base::Bind(&FirstRunDelayedTasks::OnExtensionSystemReady,
weak_ptr_factory_.GetWeakPtr(), context));
break;
}
case chrome::NOTIFICATION_BROWSER_CLOSED: {
delete this;
break;
}
default:
NOTREACHED();
}
}
private:
// Private ctor forces it to be created only in the heap.
~FirstRunDelayedTasks() override {}
void OnExtensionSystemReady(content::BrowserContext* context) {
// Process the notification and delete this.
extensions::ExtensionService* service =
extensions::ExtensionSystem::Get(context)->extension_service();
if (service) {
// Trigger an extension update check. If the extension specified in the
// master pref is older than the live extension it will get updated which
// is the same as get it installed.
service->updater()->CheckNow(extensions::ExtensionUpdater::CheckParams());
}
delete this;
}
content::NotificationRegistrar registrar_;
base::WeakPtrFactory<FirstRunDelayedTasks> weak_ptr_factory_;
};
// Installs a task to do an extensions update check once the extensions system
// is running.
void DoDelayedInstallExtensions() {
new FirstRunDelayedTasks();
}
void DoDelayedInstallExtensionsIfNeeded(
installer::MasterPreferences* install_prefs) {
base::DictionaryValue* extensions = 0;
if (install_prefs->GetExtensionsBlock(&extensions)) {
DVLOG(1) << "Extensions block found in master preferences";
DoDelayedInstallExtensions();
}
}
// Launches the import, via |importer_host|, from |source_profile| into
// |target_profile| for the items specified in the |items_to_import| bitfield.
// This may be done in a separate process depending on the platform, but it will
// always block until done.
void ImportFromSourceProfile(const importer::SourceProfile& source_profile,
Profile* target_profile,
uint16_t items_to_import) {
// Deletes itself.
ExternalProcessImporterHost* importer_host =
new ExternalProcessImporterHost;
// Don't show the warning dialog if import fails.
importer_host->set_headless();
ImportEndedObserver observer;
importer_host->set_observer(&observer);
importer_host->StartImportSettings(source_profile,
target_profile,
items_to_import,
new ProfileWriter(target_profile));
// If the import process has not errored out, block on it.
if (!observer.ended()) {
base::RunLoop loop;
observer.set_callback_for_import_end(loop.QuitClosure());
loop.Run();
observer.set_callback_for_import_end(base::Closure());
}
}
// Imports bookmarks from an html file whose path is provided by
// |import_bookmarks_path|.
void ImportFromFile(Profile* profile,
const std::string& import_bookmarks_path) {
importer::SourceProfile source_profile;
source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
const base::FilePath::StringType& import_bookmarks_path_str =
#if defined(OS_WIN)
base::UTF8ToUTF16(import_bookmarks_path);
#else
import_bookmarks_path;
#endif
source_profile.source_path = base::FilePath(import_bookmarks_path_str);
ImportFromSourceProfile(source_profile, profile, importer::FAVORITES);
g_auto_import_state |= first_run::AUTO_IMPORT_BOOKMARKS_FILE_IMPORTED;
}
// Imports settings from the first profile in |importer_list|.
void ImportSettings(Profile* profile,
std::unique_ptr<ImporterList> importer_list,
uint16_t items_to_import) {
DCHECK(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. If there is no overlap, skip.
items_to_import &= source_profile.services_supported;
if (items_to_import)
ImportFromSourceProfile(source_profile, profile, items_to_import);
g_auto_import_state |= first_run::AUTO_IMPORT_PROFILE_IMPORTED;
}
GURL UrlFromString(const std::string& in) {
return GURL(in);
}
void ConvertStringVectorToGURLVector(
const std::vector<std::string>& src,
std::vector<GURL>* ret) {
ret->resize(src.size());
std::transform(src.begin(), src.end(), ret->begin(), &UrlFromString);
}
static base::LazyInstance<base::FilePath>::DestructorAtExit
master_prefs_path_for_testing = LAZY_INSTANCE_INITIALIZER;
// Loads master preferences from the master preference file into the installer
// master preferences. Returns the pointer to installer::MasterPreferences
// object if successful; otherwise, returns NULL.
installer::MasterPreferences* LoadMasterPrefs() {
base::FilePath master_prefs_path;
if (!master_prefs_path_for_testing.Get().empty()) {
master_prefs_path = master_prefs_path_for_testing.Get();
} else {
master_prefs_path = base::FilePath(first_run::internal::MasterPrefsPath());
}
if (master_prefs_path.empty())
return NULL;
installer::MasterPreferences* install_prefs =
new installer::MasterPreferences(master_prefs_path);
if (!install_prefs->read_from_file()) {
delete install_prefs;
return NULL;
}
return install_prefs;
}
// Makes chrome the user's default browser according to policy or
// |make_chrome_default_for_user| if no policy is set.
void ProcessDefaultBrowserPolicy(bool make_chrome_default_for_user) {
// Only proceed if chrome can be made default unattended. In other cases, this
// is handled by the first run default browser prompt (on Windows 8+).
if (shell_integration::GetDefaultWebClientSetPermission() ==
shell_integration::SET_DEFAULT_UNATTENDED) {
// The policy has precedence over the user's choice.
if (g_browser_process->local_state()->IsManagedPreference(
prefs::kDefaultBrowserSettingEnabled)) {
if (g_browser_process->local_state()->GetBoolean(
prefs::kDefaultBrowserSettingEnabled)) {
shell_integration::SetAsDefaultBrowser();
}
} else if (make_chrome_default_for_user) {
shell_integration::SetAsDefaultBrowser();
}
}
}
// Reads the creation time of the first run sentinel file. If the first run
// sentinel file does not exist, it will return base::Time().
base::Time ReadFirstRunSentinelCreationTime() {
base::Time first_run_sentinel_creation_time = base::Time();
base::FilePath first_run_sentinel;
if (first_run::internal::GetFirstRunSentinelFilePath(&first_run_sentinel)) {
base::File::Info info;
if (base::GetFileInfo(first_run_sentinel, &info))
first_run_sentinel_creation_time = info.creation_time;
}
return first_run_sentinel_creation_time;
}
} // namespace
namespace first_run {
namespace internal {
void SetupMasterPrefsFromInstallPrefs(
const installer::MasterPreferences& install_prefs,
MasterPrefs* out_prefs) {
ConvertStringVectorToGURLVector(
install_prefs.GetFirstRunTabs(), &out_prefs->new_tabs);
bool value = false;
if (install_prefs.GetBool(
installer::master_preferences::kMakeChromeDefaultForUser,
&value) && value) {
out_prefs->make_chrome_default_for_user = true;
}
if (install_prefs.GetBool(
installer::master_preferences::kSuppressFirstRunDefaultBrowserPrompt,
&value) && value) {
out_prefs->suppress_first_run_default_browser_prompt = true;
}
install_prefs.GetString(
installer::master_preferences::kDistroImportBookmarksFromFilePref,
&out_prefs->import_bookmarks_path);
out_prefs->compressed_variations_seed =
install_prefs.GetCompressedVariationsSeed();
out_prefs->variations_seed_signature =
install_prefs.GetVariationsSeedSignature();
install_prefs.GetString(
installer::master_preferences::kDistroSuppressDefaultBrowserPromptPref,
&out_prefs->suppress_default_browser_prompt_for_version);
if (install_prefs.GetBool(
installer::master_preferences::kDistroWelcomePageOnOSUpgradeEnabled,
&value) &&
!value) {
out_prefs->welcome_page_on_os_upgrade_enabled = false;
}
}
bool GetFirstRunSentinelFilePath(base::FilePath* path) {
base::FilePath user_data_dir;
if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
return false;
*path = user_data_dir.Append(chrome::kFirstRunSentinel);
return true;
}
bool CreateSentinel() {
base::FilePath first_run_sentinel;
return GetFirstRunSentinelFilePath(&first_run_sentinel) &&
base::WriteFile(first_run_sentinel, "", 0) != -1;
}
// -- Platform-specific functions --
#if !defined(OS_LINUX) && !defined(OS_BSD)
bool IsOrganicFirstRun() {
std::string brand;
google_brand::GetBrand(&brand);
return google_brand::IsOrganicFirstRun(brand);
}
#endif
FirstRunState DetermineFirstRunState(bool has_sentinel,
bool force_first_run,
bool no_first_run) {
return (force_first_run || (!has_sentinel && !no_first_run))
? FIRST_RUN_TRUE
: FIRST_RUN_FALSE;
}
} // namespace internal
MasterPrefs::MasterPrefs() = default;
MasterPrefs::~MasterPrefs() = default;
void RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(prefs::kImportAutofillFormData, false);
registry->RegisterBooleanPref(prefs::kImportBookmarks, false);
registry->RegisterBooleanPref(prefs::kImportHistory, false);
registry->RegisterBooleanPref(prefs::kImportHomepage, false);
registry->RegisterBooleanPref(prefs::kImportSavedPasswords, false);
registry->RegisterBooleanPref(prefs::kImportSearchEngine, false);
}
bool IsChromeFirstRun() {
if (g_first_run == internal::FIRST_RUN_UNKNOWN) {
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
g_first_run = internal::DetermineFirstRunState(
internal::IsFirstRunSentinelPresent(),
command_line->HasSwitch(switches::kForceFirstRun),
command_line->HasSwitch(switches::kNoFirstRun));
}
return g_first_run == internal::FIRST_RUN_TRUE;
}
#if defined(OS_MACOSX)
bool IsFirstRunSuppressed(const base::CommandLine& command_line) {
return command_line.HasSwitch(switches::kNoFirstRun);
}
#endif
bool IsMetricsReportingOptIn() {
// Metrics reporting is opt-out by default for all platforms and channels.
// However, user will have chance to modify metrics reporting state during
// first run.
return false;
}
void CreateSentinelIfNeeded() {
if (IsChromeFirstRun())
internal::CreateSentinel();
// Causes the first run sentinel creation time to be read and cached, while
// I/O is still allowed.
ignore_result(GetFirstRunSentinelCreationTime());
}
base::Time GetFirstRunSentinelCreationTime() {
if (g_cached_sentinel_creation_time.is_null())
g_cached_sentinel_creation_time = ReadFirstRunSentinelCreationTime();
return g_cached_sentinel_creation_time;
}
void ResetCachedSentinelDataForTesting() {
g_cached_sentinel_creation_time = base::Time();
g_first_run = first_run::internal::FIRST_RUN_UNKNOWN;
}
void SetShouldShowWelcomePage() {
g_should_show_welcome_page = true;
}
bool ShouldShowWelcomePage() {
bool retval = g_should_show_welcome_page;
g_should_show_welcome_page = false;
return retval;
}
bool IsOnWelcomePage(content::WebContents* contents) {
const GURL welcome_page(chrome::kChromeUIWelcomeURL);
const GURL welcome_page_win10(chrome::kChromeUIWelcomeWin10URL);
const GURL current = contents->GetURL().GetWithEmptyPath();
return current == welcome_page || current == welcome_page_win10;
}
void SetShouldDoPersonalDataManagerFirstRun() {
g_should_do_autofill_personal_data_manager_first_run = true;
}
bool ShouldDoPersonalDataManagerFirstRun() {
bool retval = g_should_do_autofill_personal_data_manager_first_run;
g_should_do_autofill_personal_data_manager_first_run = false;
return retval;
}
void SetMasterPrefsPathForTesting(const base::FilePath& master_prefs) {
master_prefs_path_for_testing.Get() = master_prefs;
}
ProcessMasterPreferencesResult ProcessMasterPreferences(
const base::FilePath& user_data_dir,
MasterPrefs* out_prefs) {
DCHECK(!user_data_dir.empty());
std::unique_ptr<installer::MasterPreferences> install_prefs(
LoadMasterPrefs());
if (install_prefs.get()) {
if (!internal::ShowPostInstallEULAIfNeeded(install_prefs.get()))
return EULA_EXIT_NOW;
std::unique_ptr<base::DictionaryValue> master_dictionary =
install_prefs->master_dictionary().CreateDeepCopy();
// The distribution dictionary (and any prefs below it) are never registered
// for use in Chrome's PrefService. Strip them from the master dictionary
// before mapping it to prefs.
master_dictionary->RemoveWithoutPathExpansion(
installer::master_preferences::kDistroDict, nullptr);
if (!chrome_prefs::InitializePrefsFromMasterPrefs(
profiles::GetDefaultProfileDir(user_data_dir),
std::move(master_dictionary))) {
DLOG(ERROR) << "Failed to initialize from master_preferences.";
}
DoDelayedInstallExtensionsIfNeeded(install_prefs.get());
internal::SetupMasterPrefsFromInstallPrefs(*install_prefs, out_prefs);
}
return FIRST_RUN_PROCEED;
}
void AutoImport(
Profile* profile,
const std::string& import_bookmarks_path) {
g_auto_import_state |= AUTO_IMPORT_CALLED;
// Use |profile|'s PrefService to determine what to import. It will reflect in
// order:
// 1) Policies.
// 2) Master preferences (used to initialize user prefs in
// ProcessMasterPreferences()).
// 3) Recommended policies.
// 4) Registered default.
PrefService* prefs = profile->GetPrefs();
uint16_t items_to_import = 0;
static constexpr struct {
const char* pref_path;
importer::ImportItem bit;
} kImportItems[] = {
{prefs::kImportAutofillFormData, importer::AUTOFILL_FORM_DATA},
{prefs::kImportBookmarks, importer::FAVORITES},
{prefs::kImportHistory, importer::HISTORY},
{prefs::kImportHomepage, importer::HOME_PAGE},
{prefs::kImportSavedPasswords, importer::PASSWORDS},
{prefs::kImportSearchEngine, importer::SEARCH_ENGINES},
};
for (const auto& import_item : kImportItems) {
if (prefs->GetBoolean(import_item.pref_path))
items_to_import |= import_item.bit;
}
if (items_to_import) {
// It may be possible to do the if block below asynchronously. In which
// case, get rid of this RunLoop. http://crbug.com/366116.
base::RunLoop run_loop;
auto importer_list = std::make_unique<ImporterList>();
importer_list->DetectSourceProfiles(
g_browser_process->GetApplicationLocale(),
false, // include_interactive_profiles?
run_loop.QuitClosure());
run_loop.Run();
if (importer_list->count() > 0) {
importer::LogImporterUseToMetrics(
"AutoImport", importer_list->GetSourceProfileAt(0).importer_type);
ImportSettings(profile, std::move(importer_list), items_to_import);
}
}
if (!import_bookmarks_path.empty())
ImportFromFile(profile, import_bookmarks_path);
}
void DoPostImportTasks(Profile* profile, bool make_chrome_default_for_user) {
// Only set default browser after import as auto import relies on the current
// default browser to know what to import from.
ProcessDefaultBrowserPolicy(make_chrome_default_for_user);
SetShouldShowWelcomePage();
SetShouldDoPersonalDataManagerFirstRun();
internal::DoPostImportPlatformSpecificTasks(profile);
}
uint16_t auto_import_state() {
return g_auto_import_state;
}
} // namespace first_run