blob: 5b42555f255d8b82b8c38c3c323d1f32ddec9722 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/profile/model/profile_manager_ios_impl.h"
#import <stdint.h>
#import <utility>
#import "base/check.h"
#import "base/check_deref.h"
#import "base/feature_list.h"
#import "base/files/file_enumerator.h"
#import "base/files/file_path.h"
#import "base/functional/bind.h"
#import "base/functional/callback.h"
#import "base/metrics/histogram_functions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/task/thread_pool.h"
#import "base/threading/scoped_blocking_call.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "ios/chrome/browser/profile/model/constants.h"
#import "ios/chrome/browser/profile/model/off_the_record_profile_ios_impl.h"
#import "ios/chrome/browser/profile/model/profile_ios_impl.h"
#import "ios/chrome/browser/profile_metrics/model/profile_metrics.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_ios.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/supervised_user/model/child_account_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/list_family_members_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_service_factory.h"
namespace {
int64_t ComputeFilesSize(const base::FilePath& directory,
const base::FilePath::StringType& pattern) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
int64_t running_size = 0;
base::FileEnumerator iter(directory, false, base::FileEnumerator::FILES,
pattern);
while (!iter.Next().empty()) {
running_size += iter.GetInfo().GetSize();
}
return running_size;
}
// Simple task to log the size of the profile at `path`.
void RecordProfileSizeTask(const base::FilePath& path) {
const int64_t kBytesInOneMB = 1024 * 1024;
int64_t size = ComputeFilesSize(path, FILE_PATH_LITERAL("*"));
int size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.TotalSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("History"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.HistorySize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("History*"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.TotalHistorySize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Cookies"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.CookiesSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Bookmarks"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.BookmarksSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Favicons"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.FaviconsSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Top Sites"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.TopSitesSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Visited Links"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.VisitedLinksSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Web Data"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.WebDataSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Extension*"));
size_MB = static_cast<int>(size / kBytesInOneMB);
base::UmaHistogramCounts10000("Profile.ExtensionSize", size_MB);
}
// Returns whether `name` matches "TestProfile[0-9]+" regex which is the
// pattern used to name test profiles during an experiment run while the
// support for multi-profile was added.
bool IsTestProfile(std::string_view name) {
constexpr std::string_view kTestProfilePrefix = "TestProfile";
if (!name.starts_with(kTestProfilePrefix)) {
return false;
}
std::string_view tail = name.substr(kTestProfilePrefix.size());
if (tail.empty()) {
return false;
}
for (const char c : tail) {
if (c < '0' || '9' < c) {
return false;
}
}
return true;
}
// Returns the names of the recently active profiles.
std::set<std::string> GetRecentlyActiveProfiles(PrefService* local_state) {
std::set<std::string> profiles;
for (const auto& value : local_state->GetList(prefs::kLastActiveProfiles)) {
if (value.is_string()) {
const std::string& name = value.GetString();
if (!name.empty() && !IsTestProfile(name)) {
profiles.insert(name);
}
}
}
std::string last_used = local_state->GetString(prefs::kLastUsedProfile);
if (!last_used.empty() && !IsTestProfile(last_used)) {
profiles.insert(last_used);
}
return profiles;
}
} // namespace
BASE_FEATURE(kHideLegacyProfiles,
"HideLegacyProfiles",
base::FEATURE_ENABLED_BY_DEFAULT);
// Stores information about a single Profile.
class ProfileManagerIOSImpl::ProfileInfo {
public:
explicit ProfileInfo(std::unique_ptr<ProfileIOS> profile)
: profile_(std::move(profile)) {
DCHECK(profile_);
}
ProfileInfo(ProfileInfo&&) = default;
ProfileInfo& operator=(ProfileInfo&&) = default;
~ProfileInfo() = default;
ProfileIOS* profile() const { return profile_.get(); }
bool is_loaded() const { return is_loaded_; }
void SetIsLoaded();
void AddCallback(ProfileLoadedCallback callback);
std::vector<ProfileLoadedCallback> TakeCallbacks() {
return std::exchange(callbacks_, {});
}
private:
SEQUENCE_CHECKER(sequence_checker_);
std::unique_ptr<ProfileIOS> profile_;
std::vector<ProfileLoadedCallback> callbacks_;
bool is_loaded_ = false;
};
void ProfileManagerIOSImpl::ProfileInfo::SetIsLoaded() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!is_loaded_);
is_loaded_ = true;
}
void ProfileManagerIOSImpl::ProfileInfo::AddCallback(
ProfileLoadedCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!is_loaded_);
if (!callback.is_null()) {
callbacks_.push_back(std::move(callback));
}
}
ProfileManagerIOSImpl::ProfileManagerIOSImpl(PrefService* local_state,
const base::FilePath& data_dir)
: local_state_(local_state),
profile_data_dir_(data_dir),
profile_attributes_storage_(local_state) {
CHECK(local_state_);
CHECK(!profile_data_dir_.empty());
}
ProfileManagerIOSImpl::~ProfileManagerIOSImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers_) {
observer.OnProfileManagerDestroyed(this);
}
}
void ProfileManagerIOSImpl::AddObserver(ProfileManagerObserverIOS* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.AddObserver(observer);
// Notify the observer of any pre-existing Profiles.
for (auto& [name, profile_info] : profiles_map_) {
ProfileIOS* profile = profile_info.profile();
DCHECK(profile);
observer->OnProfileCreated(this, profile);
if (profile_info.is_loaded()) {
observer->OnProfileLoaded(this, profile);
}
}
}
void ProfileManagerIOSImpl::RemoveObserver(
ProfileManagerObserverIOS* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.RemoveObserver(observer);
}
void ProfileManagerIOSImpl::LoadProfiles() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::set<std::string> profiles = GetRecentlyActiveProfiles(local_state_);
// LoadProfiles() must load at least one profile, so if there is no
// recently active Profile, create one with a default name.
if (profiles.empty()) {
profiles.insert(kIOSChromeInitialProfile);
}
// Take care of the legacy profiles.
if (base::FeatureList::IsEnabled(kHideLegacyProfiles)) {
HideLegacyProfiles(profiles);
} else {
RestoreLegacyProfiles(profiles);
}
// Record the number of legacy profiles.
base::UmaHistogramCounts100(
"Profile.LegacyProfilesCount",
static_cast<int>(
std::min(size_t{100},
local_state_->GetDict(prefs::kLegacyProfileMap).size())));
for (const std::string& name : profiles) {
ProfileIOS* profile = CreateProfile(name);
DCHECK(profile != nullptr);
}
}
ProfileIOS* ProfileManagerIOSImpl::GetProfileWithName(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If the profile is already loaded, just return it.
auto iter = profiles_map_.find(name);
if (iter != profiles_map_.end()) {
ProfileInfo& profile_info = iter->second;
if (profile_info.is_loaded()) {
DCHECK(profile_info.profile());
return profile_info.profile();
}
}
return nullptr;
}
std::vector<ProfileIOS*> ProfileManagerIOSImpl::GetLoadedProfiles() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<ProfileIOS*> loaded_profiles;
for (const auto& [name, profile_info] : profiles_map_) {
if (profile_info.is_loaded()) {
DCHECK(profile_info.profile());
loaded_profiles.push_back(profile_info.profile());
}
}
return loaded_profiles;
}
bool ProfileManagerIOSImpl::HasProfileWithName(std::string_view name) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return profile_attributes_storage_.HasProfileWithName(name);
}
bool ProfileManagerIOSImpl::CanCreateProfileWithName(
std::string_view name) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Cannot create a profile with the same name as a legacy profile.
if (local_state_->GetDict(prefs::kLegacyProfileMap).Find(name)) {
return false;
}
// TODO(crbug.com/335630301): check whether there is a Profile with that name
// whose deletion is pending, and return false if this is the case (to avoid
// recovering its state).
return true;
}
bool ProfileManagerIOSImpl::LoadProfileAsync(
std::string_view name,
ProfileLoadedCallback initialized_callback,
ProfileLoadedCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!HasProfileWithName(name)) {
// Must not create the ProfileIOS if it does not already exist, so fail.
if (!initialized_callback.is_null()) {
std::move(initialized_callback).Run(nullptr);
}
return false;
}
return CreateProfileAsync(name, std::move(initialized_callback),
std::move(created_callback));
}
bool ProfileManagerIOSImpl::CreateProfileAsync(
std::string_view name,
ProfileLoadedCallback initialized_callback,
ProfileLoadedCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return CreateProfileWithMode(name, CreationMode::kAsynchronous,
std::move(initialized_callback),
std::move(created_callback));
}
ProfileIOS* ProfileManagerIOSImpl::LoadProfile(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!HasProfileWithName(name)) {
// Must not create the ProfileIOS if it does not already exist, so fail.
return nullptr;
}
return CreateProfile(name);
}
ProfileIOS* ProfileManagerIOSImpl::CreateProfile(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!CreateProfileWithMode(name, CreationMode::kSynchronous,
/* initialized_callback */ {},
/* created_callback */ {})) {
return nullptr;
}
auto iter = profiles_map_.find(name);
DCHECK(iter != profiles_map_.end());
DCHECK(iter->second.is_loaded());
return iter->second.profile();
}
void ProfileManagerIOSImpl::UnloadProfile(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto iter = profiles_map_.find(name);
DCHECK(iter != profiles_map_.end());
ProfileInfo profile_info = std::move(iter->second);
profiles_map_.erase(iter);
if (!profile_info.is_loaded()) {
// The profile is unloaded before it could be fully loaded, notify
// any pending callback that the load has failed.
for (auto& callback : profile_info.TakeCallbacks()) {
std::move(callback).Run(nullptr);
}
} else {
for (auto& observer : observers_) {
observer.OnProfileUnloaded(this, profile_info.profile());
}
}
}
void ProfileManagerIOSImpl::UnloadAllProfiles() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
while (!profiles_map_.empty()) {
const std::string& name = profiles_map_.begin()->first;
UnloadProfile(name);
}
}
ProfileAttributesStorageIOS*
ProfileManagerIOSImpl::GetProfileAttributesStorage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return &profile_attributes_storage_;
}
void ProfileManagerIOSImpl::OnProfileCreationStarted(
ProfileIOS* profile,
CreationMode creation_mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(profile);
for (auto& observer : observers_) {
observer.OnProfileCreated(this, profile);
}
}
void ProfileManagerIOSImpl::OnProfileCreationFinished(
ProfileIOS* profile,
CreationMode creation_mode,
bool is_new_profile,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(profile);
DCHECK(!profile->IsOffTheRecord());
// If the Profile is loaded synchronously the method is called as part of the
// constructor and before the ProfileInfo insertion in the map. The method
// will be called again after the insertion.
auto iter = profiles_map_.find(profile->GetProfileName());
if (iter == profiles_map_.end()) {
DCHECK(creation_mode == CreationMode::kSynchronous);
return;
}
DCHECK(iter != profiles_map_.end());
auto callbacks = iter->second.TakeCallbacks();
if (success) {
DoFinalInit(profile);
iter->second.SetIsLoaded();
} else {
if (is_new_profile) {
// TODO(crbug.com/335630301): Mark the data for removal and prevent the
// creation of a profile with the same name until the data has been
// deleted.
const std::string& name = profile->GetProfileName();
profile_attributes_storage_.RemoveProfile(name);
DCHECK(!HasProfileWithName(name));
}
profile = nullptr;
profiles_map_.erase(iter);
}
// Invoke the callbacks, if the load failed, `profile` will be null.
for (auto& callback : callbacks) {
std::move(callback).Run(profile);
}
// Notify the observers after invoking the callbacks in case of success.
if (success) {
DCHECK(profile);
for (auto& observer : observers_) {
observer.OnProfileLoaded(this, profile);
}
}
}
bool ProfileManagerIOSImpl::CreateProfileWithMode(
std::string_view name,
CreationMode creation_mode,
ProfileLoadedCallback initialized_callback,
ProfileLoadedCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool inserted = false;
bool existing = HasProfileWithName(name);
auto iter = profiles_map_.find(name);
if (iter == profiles_map_.end()) {
if (!CanCreateProfileWithName(name)) {
if (!initialized_callback.is_null()) {
std::move(initialized_callback).Run(nullptr);
}
return false;
}
if (!existing) {
profile_attributes_storage_.AddProfile(name);
DCHECK(HasProfileWithName(name));
}
// If this is the first profile ever loaded, mark it as the personal
// profile.
if (profile_attributes_storage_.GetPersonalProfileName().empty()) {
profile_attributes_storage_.SetPersonalProfileName(name);
}
std::tie(iter, inserted) = profiles_map_.insert(std::make_pair(
std::string(name),
ProfileInfo(ProfileIOS::CreateProfile(profile_data_dir_.Append(name),
name, creation_mode, this))));
DCHECK(inserted);
}
DCHECK(iter != profiles_map_.end());
ProfileInfo& profile_info = iter->second;
DCHECK(profile_info.profile());
if (!created_callback.is_null()) {
std::move(created_callback).Run(profile_info.profile());
}
if (!initialized_callback.is_null()) {
if (inserted || !profile_info.is_loaded()) {
profile_info.AddCallback(std::move(initialized_callback));
} else {
std::move(initialized_callback).Run(profile_info.profile());
}
}
// If asked to load synchronously but an asynchronous load was already in
// progress, pretend the load failed, as we cannot return an uninitialized
// Profile, nor can we wait for the asynchronous initialisation to complete.
if (creation_mode == CreationMode::kSynchronous) {
if (!inserted && !profile_info.is_loaded()) {
return false;
}
}
// If the Profile was just created, and the creation mode is synchronous then
// OnProfileCreationFinished() will have been called during the construction
// of the ProfileInfo. Thus it is necessary to call the method again here.
if (inserted && creation_mode == CreationMode::kSynchronous) {
OnProfileCreationFinished(profile_info.profile(),
CreationMode::kAsynchronous, !existing,
/* success */ true);
}
return true;
}
void ProfileManagerIOSImpl::DoFinalInit(ProfileIOS* profile) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DoFinalInitForServices(profile);
// Log the profile size after a reasonable startup delay.
DCHECK(!profile->IsOffTheRecord());
base::ThreadPool::PostDelayedTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&RecordProfileSizeTask, profile->GetStatePath()),
base::Seconds(112));
LogNumberOfProfiles(this);
}
void ProfileManagerIOSImpl::DoFinalInitForServices(ProfileIOS* profile) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
IdentityManagerFactory::GetForProfile(profile)->OnNetworkInitialized();
// Those services needs to be explicitly initialized and can't simply be
// marked as created with the profile as 1. they depend on initialisation
// performed in ProfileIOSImpl (thus can't work with TestProfileIOS), and
// 2. code do not expect them to be null (thus tests cannot be configured
// to have a null instance).
ChildAccountServiceFactory::GetForProfile(profile)->Init();
SupervisedUserServiceFactory::GetForProfile(profile)->Init();
ListFamilyMembersServiceFactory::GetForProfile(profile)->Init();
}
void ProfileManagerIOSImpl::HideLegacyProfiles(
const std::set<std::string>& profiles) {
CHECK(base::FeatureList::IsEnabled(kHideLegacyProfiles));
if (local_state_->GetBoolean(prefs::kLegacyProfileHidden)) {
return;
}
base::Value::Dict legacy_profiles;
const size_t count = profile_attributes_storage_.GetNumberOfProfiles();
for (size_t i = 0; i < count; ++i) {
const size_t index = count - i - 1; // iterate backwards
ProfileAttributesIOS attr =
profile_attributes_storage_.GetAttributesForProfileAtIndex(index);
const std::string name = attr.GetProfileName();
if (!base::Contains(profiles, name)) {
legacy_profiles.Set(name, std::move(attr).GetStorage());
profile_attributes_storage_.RemoveProfile(name);
}
}
local_state_->SetBoolean(prefs::kLegacyProfileHidden, true);
local_state_->SetDict(prefs::kLegacyProfileMap, std::move(legacy_profiles));
}
void ProfileManagerIOSImpl::RestoreLegacyProfiles(
const std::set<std::string>& profiles) {
CHECK(!base::FeatureList::IsEnabled(kHideLegacyProfiles));
if (!local_state_->GetBoolean(prefs::kLegacyProfileHidden)) {
return;
}
const base::Value::Dict& legacy_profiles =
local_state_->GetDict(prefs::kLegacyProfileMap);
for (const auto [key, value] : legacy_profiles) {
DCHECK(!base::Contains(profiles, key));
DCHECK(value.is_dict());
profile_attributes_storage_.AddProfile(key);
profile_attributes_storage_.UpdateAttributesForProfileWithName(
key, base::BindOnce(
[](const base::Value::Dict* dict, ProfileAttributesIOS attr) {
return ProfileAttributesIOS::WithAttrs(attr.GetProfileName(),
CHECK_DEREF(dict));
},
&value.GetDict()));
}
local_state_->ClearPref(prefs::kLegacyProfileHidden);
local_state_->ClearPref(prefs::kLegacyProfileMap);
}