chromium/ios/chrome/browser/profile/model/profile_manager_ios_impl.mm

// 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);
}