blob: 2b4a963edf5ff0f5fe98e1be506a3cb4c37697aa [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/barrier_closure.h"
#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/files/file_util.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 "base/uuid.h"
#import "components/prefs/pref_service.h"
#import "components/prefs/scoped_user_pref_update.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "ios/chrome/browser/profile/model/off_the_record_profile_ios_impl.h"
#import "ios/chrome/browser/profile/model/profile_deleter_ios.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/model/profile/scoped_profile_keep_alive_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);
}
} // namespace
// Stores information about a single Profile.
class ProfileManagerIOSImpl::ProfileInfo {
public:
explicit ProfileInfo(std::unique_ptr<ProfileIOS> profile)
: profile_(std::move(profile)) {
CHECK(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_, {});
}
// Increment the keep alive counter and return its value.
uint32_t IncrementKeepAliveCounter() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_LT(keep_alive_counter_, std::numeric_limits<uint32_t>::max());
return ++keep_alive_counter_;
}
// Decrement the keep alive counter and return its value.
uint32_t DecrementKeepAliveCounter() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_GT(keep_alive_counter_, 0u);
return --keep_alive_counter_;
}
private:
SEQUENCE_CHECKER(sequence_checker_);
std::unique_ptr<ProfileIOS> profile_;
std::vector<ProfileLoadedCallback> callbacks_;
uint32_t keep_alive_counter_ = 0;
bool is_loaded_ = false;
};
void ProfileManagerIOSImpl::ProfileInfo::SetIsLoaded() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!is_loaded_);
is_loaded_ = true;
}
void ProfileManagerIOSImpl::ProfileInfo::AddCallback(
ProfileLoadedCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!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());
profile_attributes_storage_.EnsurePersonalProfileExists();
}
ProfileManagerIOSImpl::~ProfileManagerIOSImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(will_be_destroyed_);
for (auto& observer : observers_) {
observer.OnProfileManagerDestroyed(this);
}
}
void ProfileManagerIOSImpl::PrepareForDestruction() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
will_be_destroyed_ = true;
// Drops the ScopedProfileKeepAliveIOS for all profiles that are still
// loading before notifying the observers. Then check that there are
// no profiles still kept alive.
loading_profiles_map_.clear();
for (auto& observer : observers_) {
observer.OnProfileManagerWillBeDestroyed(this);
}
CHECK(profiles_map_.empty());
}
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_) {
if (IsProfileMarkedForDeletion(name)) {
continue;
}
ProfileIOS* profile = profile_info.profile();
CHECK(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);
}
ProfileIOS* ProfileManagerIOSImpl::GetProfileWithName(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Do not give access to profiles marked for deletion.
if (IsProfileMarkedForDeletion(name)) {
return nullptr;
}
// 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()) {
CHECK(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 (IsProfileMarkedForDeletion(name)) {
continue;
}
if (profile_info.is_loaded()) {
CHECK(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_);
return profile_attributes_storage_.CanCreateProfileWithName(name);
}
std::string ProfileManagerIOSImpl::ReserveNewProfileName() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return profile_attributes_storage_.ReserveNewProfileName();
}
bool ProfileManagerIOSImpl::CanDeleteProfileWithName(
std::string_view name) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return profile_attributes_storage_.CanDeleteProfileWithName(name);
}
bool ProfileManagerIOSImpl::LoadProfileAsync(
std::string_view name,
ProfileLoadedCallback initialized_callback,
ProfileLoadedCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return CreateOrLoadProfile(name, LoadOrCreatePolicy::kLoadOnly,
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 CreateOrLoadProfile(name, LoadOrCreatePolicy::kCreateIfNecessary,
std::move(initialized_callback),
std::move(created_callback));
}
void ProfileManagerIOSImpl::MarkProfileForDeletion(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(CanDeleteProfileWithName(name));
// Remove the profile from the ProfileAttributesStorageIOS to prevent
// people iterating over all profiles from seeing it anymore.
profile_attributes_storage_.MarkProfileForDeletion(name);
// If the profile is not loaded, nor loading, return.
auto iter = profiles_map_.find(name);
if (iter == profiles_map_.end()) {
return;
}
// If the profile is still loading, pretend that the loading failed
// by calling the ProfileLoadedCallbacks with nullptr.
ProfileInfo& info = iter->second;
if (!info.is_loaded()) {
for (auto& callback : info.TakeCallbacks()) {
std::move(callback).Run(CreateScopedProfileKeepAlive(nullptr));
}
} else {
ProfileIOS* profile = info.profile();
for (auto& observer : observers_) {
observer.OnProfileMarkedForPermanentDeletion(this, profile);
}
}
}
bool ProfileManagerIOSImpl::IsProfileMarkedForDeletion(
std::string_view name) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return profile_attributes_storage_.IsProfileMarkedForDeletion(name);
}
void ProfileManagerIOSImpl::PurgeProfilesMarkedForDeletion(
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Get the list of profiles marked for deletion, and ignore those that
// are already loaded or loading (their data will be deleted when they
// are unloaded).
std::set<std::string> profiles =
profile_attributes_storage_.GetProfilesMarkedForDeletion();
for (const auto& [key, _] : profiles_map_) {
profiles.erase(key);
}
// If there are no profiles to delete, the operation is complete. Post
// the callback on the current sequence to ensure it is always invoked
// asynchronously.
if (profiles.empty()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(callback));
return;
}
auto closure = base::BarrierClosure(profiles.size(), std::move(callback));
for (const std::string& name : profiles) {
profile_deleter_.DeleteProfile(
name, profile_data_dir_,
base::BindOnce(&ProfileManagerIOSImpl::OnProfileDeletionComplete,
weak_ptr_factory_.GetWeakPtr(), closure, name));
}
}
ProfileAttributesStorageIOS*
ProfileManagerIOSImpl::GetProfileAttributesStorage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return &profile_attributes_storage_;
}
base::FilePath ProfileManagerIOSImpl::GetProfilePath(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(profile_attributes_storage_.HasProfileWithName(name));
return profile_data_dir_.Append(name);
}
void ProfileManagerIOSImpl::OnProfileCreationStarted(
ProfileIOS* profile,
CreationMode creation_mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_EQ(creation_mode, CreationMode::kAsynchronous);
CHECK(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_);
CHECK_EQ(creation_mode, CreationMode::kAsynchronous);
CHECK(profile);
CHECK(!profile->IsOffTheRecord());
const std::string& name = profile->GetProfileName();
auto iter = profiles_map_.find(name);
CHECK(iter != profiles_map_.end());
ProfileInfo& profile_info = iter->second;
auto callbacks = profile_info.TakeCallbacks();
// Update the ProfileAttributesStorageIOS before notifying the observers
// and callbacks of the success or failure of the operation.
if (is_new_profile) {
if (success) {
profile_attributes_storage_.UpdateAttributesForProfileWithName(
name, base::BindOnce([](ProfileAttributesIOS& attrs) {
attrs.ClearIsNewProfile();
}));
} else {
MarkProfileForDeletion(name);
}
}
// If the profile is marked for deletion, consider the load as failed
// even in case of success.
if (success && !IsProfileMarkedForDeletion(name)) {
DoFinalInit(profile);
iter->second.SetIsLoaded();
} else {
profile = nullptr;
}
// Invoke the callbacks, if the load failed, `profile` will be null.
for (auto& callback : callbacks) {
std::move(callback).Run(CreateScopedProfileKeepAlive(&profile_info));
}
// Notify the observers after invoking the callbacks in case of success.
if (success) {
CHECK(profile);
for (auto& observer : observers_) {
observer.OnProfileLoaded(this, profile);
}
}
// The profile is fully loaded, so drop the ScopedProfileKeepAliveIOS
// owned by this instance. If no other code keeps the profile alive,
// it will be unloaded at this point.
CHECK(base::Contains(loading_profiles_map_, name));
loading_profiles_map_.erase(name);
}
bool ProfileManagerIOSImpl::CreateOrLoadProfile(
std::string_view name,
LoadOrCreatePolicy policy,
ProfileLoadedCallback initialized_callback,
ProfileLoadedCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Profile creation is forbiden for profiles that have been marked for
// deletion. Fail even if the profile is already loaded, to avoid new
// usages after the deletion.
if (will_be_destroyed_ || IsProfileMarkedForDeletion(name)) {
if (!initialized_callback.is_null()) {
std::move(initialized_callback)
.Run(CreateScopedProfileKeepAlive(nullptr));
}
return false;
}
// Need to track whether the profile is already reserved, and/or loaded.
bool inserted = false;
bool existing = HasProfileWithName(name);
// As the name may have been registered with ProfileAttributesStorageIOS,
// a profile is considered as a new profile if the storage does not know
// about it, or if the IsNewProfile() flag is still set. The flag will be
// cleared the first time the profile is successfully loaded.
bool is_new_profile = !existing;
if (existing) {
const ProfileAttributesIOS attrs =
profile_attributes_storage_.GetAttributesForProfileWithName(name);
is_new_profile = attrs.IsNewProfile();
}
auto iter = profiles_map_.find(name);
if (iter == profiles_map_.end()) {
if (is_new_profile) {
// The profile has never be loaded and needs to be created. Check
// whether creating the profile is allowed. This is controlled by
// `policy` and `CanCreateProfileWithName(...)`.
const bool creation_allowed =
(policy == LoadOrCreatePolicy::kCreateIfNecessary) &&
(existing || CanCreateProfileWithName(name));
if (!creation_allowed) {
if (!initialized_callback.is_null()) {
std::move(initialized_callback)
.Run(CreateScopedProfileKeepAlive(nullptr));
}
return false;
}
}
if (!existing) {
profile_attributes_storage_.AddProfile(name);
CHECK(HasProfileWithName(name));
}
std::tie(iter, inserted) = profiles_map_.emplace(
name, ProfileIOS::CreateProfile(profile_data_dir_.Append(name), name,
CreationMode::kAsynchronous, this));
CHECK(inserted);
}
CHECK(iter != profiles_map_.end());
ProfileInfo& profile_info = iter->second;
CHECK(profile_info.profile());
// Ensure the profile is kept alive until it is fully loaded or
// the current instance is destroyed.
if (inserted) {
CHECK(!base::Contains(loading_profiles_map_, name));
loading_profiles_map_.emplace(name,
CreateScopedProfileKeepAlive(&profile_info));
}
if (!created_callback.is_null()) {
std::move(created_callback)
.Run(CreateScopedProfileKeepAlive(&profile_info));
}
if (!initialized_callback.is_null()) {
if (inserted || !profile_info.is_loaded()) {
profile_info.AddCallback(std::move(initialized_callback));
} else {
std::move(initialized_callback)
.Run(CreateScopedProfileKeepAlive(&profile_info));
}
}
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.
CHECK(!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();
ListFamilyMembersServiceFactory::GetForProfile(profile)->Init();
}
void ProfileManagerIOSImpl::OnProfileDeletionComplete(
base::OnceClosure closure,
const std::string& profile_name,
ProfileDeleterIOS::Result result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (result == ProfileDeleterIOS::Result::kSuccess) {
profile_attributes_storage_.ProfileDeletionComplete(profile_name);
}
std::move(closure).Run();
}
ScopedProfileKeepAliveIOS ProfileManagerIOSImpl::CreateScopedProfileKeepAlive(
ProfileInfo* info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If the info is null or the current instance is being destroyed, then
// the ScopedProfileKeepAlive is a no-op.
if (!info || will_be_destroyed_) {
return ScopedProfileKeepAliveIOS(CreatePassKey(), nullptr, {});
}
ProfileIOS* profile = info->profile();
CHECK(info->profile());
info->IncrementKeepAliveCounter();
return ScopedProfileKeepAliveIOS(
CreatePassKey(), profile,
base::BindOnce(&ProfileManagerIOSImpl::MaybeUnloadProfile,
weak_ptr_factory_.GetWeakPtr(),
profile->GetProfileName()));
}
void ProfileManagerIOSImpl::MaybeUnloadProfile(std::string_view name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto iter = profiles_map_.find(name);
CHECK(iter != profiles_map_.end());
// If there are other ScopedProfileKeepAliveIOS referencing this profile,
// then return as there is nothing more to do.
if (iter->second.DecrementKeepAliveCounter() > 0u) {
return;
}
// Extract the profile from the storage before notifying the observers.
auto node = profiles_map_.extract(iter);
ProfileInfo& info = node.mapped();
// If the profile is still loading, pretend that the loading failed
// by calling the ProfileLoadedCallbacks with nullptr.
if (!info.is_loaded()) {
for (auto& callback : info.TakeCallbacks()) {
std::move(callback).Run(CreateScopedProfileKeepAlive(nullptr));
}
} else {
// If profile is loaded, notify all observers that it is unloaded.
ProfileIOS* profile = info.profile();
for (auto& observer : observers_) {
observer.OnProfileUnloaded(this, profile);
}
}
// If the profile has been marked for deletion, then try to delete it
// after notifying all observers that it has been unloaded.
if (IsProfileMarkedForDeletion(node.key())) {
profile_deleter_.DeleteProfile(
node.key(), profile_data_dir_,
base::BindOnce(&ProfileManagerIOSImpl::OnProfileDeletionComplete,
weak_ptr_factory_.GetWeakPtr(), base::DoNothing(),
node.key()));
}
}