// 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/files/file_enumerator.h"
#import "base/files/file_path.h"
#import "base/functional/bind.h"
#import "base/functional/callback.h"
#import "base/metrics/histogram_macros.h"
#import "base/strings/utf_string_conversions.h"
#import "base/task/thread_pool.h"
#import "base/threading/scoped_blocking_call.h"
#import "components/optimization_guide/core/optimization_guide_features.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "ios/chrome/browser/browser_state/model/constants.h"
#import "ios/chrome/browser/browser_state_metrics/model/browser_state_metrics.h"
#import "ios/chrome/browser/optimization_guide/model/optimization_guide_service.h"
#import "ios/chrome/browser/optimization_guide/model/optimization_guide_service_factory.h"
#import "ios/chrome/browser/page_info/about_this_site_service_factory.h"
#import "ios/chrome/browser/plus_addresses/model/plus_address_service_factory.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/push_notification/model/push_notification_profile_service_factory.h"
#import "ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.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/account_consistency_service_factory.h"
#import "ios/chrome/browser/signin/model/account_reconcilor_factory.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"
#import "ios/chrome/browser/unified_consent/model/unified_consent_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);
UMA_HISTOGRAM_COUNTS_10000("Profile.TotalSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("History"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.HistorySize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("History*"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.TotalHistorySize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Cookies"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.CookiesSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Bookmarks"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.BookmarksSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Favicons"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.FaviconsSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Top Sites"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.TopSitesSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Visited Links"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.VisitedLinksSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Web Data"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.WebDataSize", size_MB);
size = ComputeFilesSize(path, FILE_PATH_LITERAL("Extension*"));
size_MB = static_cast<int>(size / kBytesInOneMB);
UMA_HISTOGRAM_COUNTS_10000("Profile.ExtensionSize", size_MB);
}
} // namespace
// 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> last_active_profile_names;
for (const base::Value& profile_name :
local_state_->GetList(prefs::kLastActiveProfiles)) {
if (profile_name.is_string()) {
last_active_profile_names.insert(profile_name.GetString());
}
}
// Ensure that the last used Profile is loaded (since client code does not
// expect GetLastUsedProfileDeprecatedDoNotUse() to return null).
//
// See https://crbug.com/345478758 for exemple of crashes happening when the
// last used Profile is not loaded.
last_active_profile_names.insert(GetLastUsedProfileName());
// Create and load test profiles if experiment enabling Switch Profile
// developer UI is enabled.
std::optional<int> load_test_profiles =
experimental_flags::DisplaySwitchProfile();
if (load_test_profiles.has_value()) {
for (int i = 0; i < load_test_profiles; i++) {
last_active_profile_names.insert("TestProfile" +
base::NumberToString(i + 1));
}
}
for (const std::string& profile_name : last_active_profile_names) {
ProfileIOS* profile = CreateProfile(profile_name);
DCHECK(profile != nullptr);
}
}
ProfileIOS* ProfileManagerIOSImpl::GetLastUsedProfileDeprecatedDoNotUse() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ProfileIOS* profile = GetProfileWithName(GetLastUsedProfileName());
CHECK(profile);
return profile;
}
ProfileIOS* ProfileManagerIOSImpl::GetProfileWithName(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If the browser state 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() {
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::LoadProfileAsync(
std::string_view name,
ProfileLoadedCallback initialized_callback,
ProfileLoadedCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!ProfileWithNameExists(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 (!ProfileWithNameExists(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();
}
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(!ProfileWithNameExists(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);
}
}
}
std::string ProfileManagerIOSImpl::GetLastUsedProfileName() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::string last_used_profile_name =
local_state_->GetString(prefs::kLastUsedProfile);
if (last_used_profile_name.empty()) {
last_used_profile_name = kIOSChromeInitialBrowserState;
}
CHECK(!last_used_profile_name.empty());
return last_used_profile_name;
}
bool ProfileManagerIOSImpl::ProfileWithNameExists(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return profile_attributes_storage_.HasProfileWithName(name);
}
bool ProfileManagerIOSImpl::CanCreateProfileWithName(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// 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::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 = ProfileWithNameExists(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(ProfileWithNameExists(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 unitialized
// 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_);
ios::AccountConsistencyServiceFactory::GetForBrowserState(profile);
IdentityManagerFactory::GetForBrowserState(profile)->OnNetworkInitialized();
ios::AccountReconcilorFactory::GetForBrowserState(profile);
// Initialization needs to happen after the browser context is available
// because UnifiedConsentService's dependencies needs the URL context getter.
UnifiedConsentServiceFactory::GetForBrowserState(profile);
// Initialization needs to happen after the profile is available because
// IOSChromeMetricsServiceAccessor requires profile to be registered in the
// ProfileManagerIOS.
if (optimization_guide::features::IsOptimizationHintsEnabled()) {
OptimizationGuideServiceFactory::GetForBrowserState(profile)->DoFinalInit(
BackgroundDownloadServiceFactory::GetForBrowserState(profile));
}
segmentation_platform::SegmentationPlatformServiceFactory::GetForBrowserState(
profile);
PushNotificationBrowserStateServiceFactory::GetForBrowserState(profile);
ChildAccountServiceFactory::GetForBrowserState(profile)->Init();
SupervisedUserServiceFactory::GetForBrowserState(profile)->Init();
ListFamilyMembersServiceFactory::GetForBrowserState(profile)->Init();
// The AboutThisSiteService needs to be created at startup in order to
// register its OptimizationType with OptimizationGuideDecider.
AboutThisSiteServiceFactory::GetForBrowserState(profile);
PlusAddressServiceFactory::GetForBrowserState(profile);
}