blob: 3ce60dd4b78b5b34a5eec33286e66099df56e66d [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_ios_impl.h"
#import <Foundation/Foundation.h>
#import <utility>
#import "base/apple/backup_util.h"
#import "base/check.h"
#import "base/feature_list.h"
#import "base/files/file_path.h"
#import "base/files/file_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/task/sequenced_task_runner.h"
#import "base/threading/thread_restrictions.h"
#import "base/uuid.h"
#import "components/bookmarks/browser/bookmark_model.h"
#import "components/content_settings/core/browser/host_content_settings_map.h"
#import "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#import "components/policy/core/common/configuration_policy_provider.h"
#import "components/policy/core/common/schema_registry.h"
#import "components/pref_registry/pref_registry_syncable.h"
#import "components/prefs/json_pref_store.h"
#import "components/prefs/pref_service.h"
#import "components/profile_metrics/browser_profile_type.h"
#import "components/proxy_config/ios/proxy_service_factory.h"
#import "components/proxy_config/pref_proxy_config_tracker.h"
#import "components/supervised_user/core/browser/supervised_user_content_settings_provider.h"
#import "components/supervised_user/core/browser/supervised_user_pref_store.h"
#import "components/supervised_user/core/browser/supervised_user_settings_service.h"
#import "components/supervised_user/core/common/features.h"
#import "components/sync_preferences/pref_service_syncable.h"
#import "components/user_prefs/user_prefs.h"
#import "ios/chrome/browser/content_settings/model/host_content_settings_map_factory.h"
#import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h"
#import "ios/chrome/browser/policy/model/profile_policy_connector.h"
#import "ios/chrome/browser/policy/model/profile_policy_connector_factory.h"
#import "ios/chrome/browser/policy/model/schema_registry_factory.h"
#import "ios/chrome/browser/prefs/model/ios_chrome_pref_service_factory.h"
#import "ios/chrome/browser/profile/model/constants.h"
#import "ios/chrome/browser/profile/model/ios_chrome_url_request_context_getter.h"
#import "ios/chrome/browser/profile/model/off_the_record_profile_ios_impl.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/paths/paths_internal.h"
#import "ios/chrome/browser/shared/model/prefs/browser_prefs.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/features.h"
#import "ios/chrome/browser/shared/model/profile/profile_dependency_manager_ios.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_settings_service_factory.h"
#import "ios/web/public/thread/web_thread.h"
// TODO(crbug.com/369296278): Remove when MaybeMigrateSyncingUserToSignedIn()
// is no longer used (i.e. ~one year after kForceMigrateSyncingUserToSignedIn
// is fully launched).
#import "base/task/bind_post_task.h"
#import "components/browser_sync/sync_to_signin_migration.h"
namespace {
// Determine the WebKit storage UUID for profile.
//
// There are three possibilities depending on the age of the profile.
// First for really old profiles (before the introduction of separate
// data store in iOS 17.0) they store the data in the global unnamed
// store, and thus use an empty UUID to represent this. For profiles
// created between M-128 and M-133, a random UUID is generated and is
// stored in the PrefService to use as the identifier for WebKit data.
// Profile created in M-133+ have their name be an UUID and the name
// can use used as the storage identifier.
//
// To determine which value to use for the storage identifier, proceed
// in steps. If the profile name can be parsed as an UUID, then use it
// as the storage identifier (this is a profile created in M-133+). If
// not, then use the value stored in kBrowserStateStorageIdentifier if
// it is set, otherwise do not use an UUID (the profile is legacy and
// will use the global storage).
//
// Assert that the profile name is an UUID if it has just been created
// to ensure the algorithm above correctly cover all possible cases.
//
// There is no WebKit API to change the storage nor to copy data from
// one storage location to another. Until an API is provided, it will
// not be possible to migrate users. For this reason the state is not
// recorded as an histogram (it could be years before we can drop the
// support for "default" storage or for storing the UUID in prefs).
base::Uuid GetStorageUUID(const std::string& name,
PrefService* prefs,
bool is_new_profile) {
base::Uuid uuid = base::Uuid::ParseLowercase(name);
if (uuid.is_valid()) {
// M-133+ profile whose name is an UUID already. In that case use
// the profile name as the storage identifier.
return uuid;
}
const std::string& uuid_string =
prefs->GetString(prefs::kBrowserStateStorageIdentifier);
if (uuid_string.empty() && !is_new_profile) {
// Pre M-128 profile, there is no UUID stored in the prefs and the
// profile use the default WebKit storage, which is represented as
// an invalid UUID.
return base::Uuid();
}
// M-128+ profile by default.
//
// Note: `uuid_string` should be a valid UUID and `is_new_profile`
// should be false, but the code will still generate a random UUID
// if either of those assumption are incorrect.
//
// They could happen if the storage for a profile was tampered with
// or somehow got corrupted (e.g. the profile directory was deleted
// or the json data on disk modified). Another possibility is that
// the name was reserved in ProfileAttributesStorageIOS but for some
// reason the profile never created.
uuid = base::Uuid::ParseCaseInsensitive(uuid_string);
if (!uuid.is_valid()) {
uuid = base::Uuid::GenerateRandomV4();
prefs->SetString(prefs::kBrowserStateStorageIdentifier,
uuid.AsLowercaseString());
}
return uuid;
}
} // namespace
// Struct storing informations that is needed for prefs initialisation.
struct ProfileIOSImpl::InitInfo {
CreationMode creation_mode;
bool is_new_profile;
base::FilePath cache_path;
scoped_refptr<SupervisedUserPrefStore> supervised_user_prefs;
};
// Helper class to create the Profile's directory.
//
// This is a separate class to limit how much code can be allowed to block
// the main sequence. It is required to block the sequence because we need
// to synchronously create the directories used to store the Profile data.
class BrowserStateDirectoryBuilder {
public:
// Stores the result of creating the directories.
struct [[nodiscard]] Result {
static Result Success(bool is_new_profile, base::FilePath cache) {
return Result{
.success = true,
.created = is_new_profile,
.cache_path = std::move(cache),
};
}
static Result Failure() {
return Result{
.success = false,
.created = false,
};
}
const bool success;
const bool created;
const base::FilePath cache_path;
};
// Creates the directories if possible.
static Result CreateDirectories(const base::FilePath& state_path,
const base::FilePath& otr_path);
};
// static
BrowserStateDirectoryBuilder::Result
BrowserStateDirectoryBuilder::CreateDirectories(
const base::FilePath& state_path,
const base::FilePath& otr_path) {
// Create the profile directory synchronously otherwise we would need to
// sequence every otherwise independent I/O operation inside the profile
// directory with this operation. base::CreateDirectory() should be a
// lightweight I/O operation and avoiding the headache of sequencing all
// otherwise unrelated I/O after this one justifies running it on the main
// thread.
base::ScopedAllowBlocking allow_blocking_to_create_directory;
bool created = false;
if (!base::PathExists(state_path)) {
if (!base::CreateDirectory(state_path)) {
return Result::Failure();
}
created = true;
}
// Create the directory for the OTR stash state now, even though it won't
// necessarily be needed: the OTR profile itself is created
// synchronously on an as-needed basis on the UI thread, so creation of its
// stash state directory cannot easily be done at that point.
if (!base::PathExists(otr_path)) {
if (!base::CreateDirectory(otr_path)) {
return Result::Failure();
}
}
base::apple::SetBackupExclusion(otr_path);
base::FilePath base_cache_path;
ios::GetUserCacheDirectory(state_path, &base_cache_path);
base::FilePath cache_path = base_cache_path.Append(kIOSChromeCacheDirname);
if (!base::PathExists(cache_path)) {
if (!base::CreateDirectory(cache_path)) {
return Result::Failure();
}
}
return Result::Success(created, cache_path);
}
// static
std::unique_ptr<ProfileIOS> ProfileIOS::CreateProfile(
const base::FilePath& path,
std::string_view profile_name,
CreationMode creation_mode,
Delegate* delegate) {
// Get sequenced task runner for making sure that file operations of
// this profile are executed in expected order (what was previously assured by
// the FILE thread).
scoped_refptr<base::SequencedTaskRunner> io_task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskShutdownBehavior::BLOCK_SHUTDOWN, base::MayBlock()});
return base::WrapUnique(new ProfileIOSImpl(path, profile_name, io_task_runner,
creation_mode, delegate));
}
ProfileIOSImpl::ProfileIOSImpl(
const base::FilePath& state_path,
std::string_view profile_name,
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
CreationMode creation_mode,
Delegate* delegate)
: ProfileIOS(state_path, profile_name, std::move(io_task_runner)),
delegate_(delegate),
pref_registry_(new user_prefs::PrefRegistrySyncable),
io_data_(new ProfileIOSImplIOData::Handle(this)) {
DCHECK(!profile_name.empty());
ProfileDependencyManagerIOS::GetInstance()->MarkProfileLive(this);
profile_metrics::SetBrowserProfileType(
this, profile_metrics::BrowserProfileType::kRegular);
if (delegate_) {
delegate_->OnProfileCreationStarted(this, creation_mode);
}
const BrowserStateDirectoryBuilder::Result directories_creation_result =
BrowserStateDirectoryBuilder::CreateDirectories(
state_path, GetOffTheRecordStatePath());
DCHECK(directories_creation_result.success);
// Bring up the policy system before creating `prefs_`.
BrowserPolicyConnectorIOS* connector =
GetApplicationContext()->GetBrowserPolicyConnector();
DCHECK(connector);
policy_schema_registry_ = BuildSchemaRegistryForProfile(
this, connector->GetChromeSchema(), connector->GetSchemaRegistry());
// Create the UserCloudPolicyManager and start it immediately if the
// Profile is loaded synchronously.
user_cloud_policy_manager_ = policy::UserCloudPolicyManager::Create(
state_path, policy_schema_registry_.get(),
creation_mode == CreationMode::kSynchronous, GetIOTaskRunner(),
base::BindRepeating(&ApplicationContext::GetNetworkConnectionTracker,
base::Unretained(GetApplicationContext())));
policy_connector_ = BuildProfilePolicyConnector(
policy_schema_registry_.get(), connector,
user_cloud_policy_manager_.get(),
user_cloud_policy_manager_ && user_cloud_policy_manager_->core()
? user_cloud_policy_manager_->core()->store()
: nullptr);
// Register Profile preferences.
RegisterProfilePrefs(pref_registry_.get());
ProfileDependencyManagerIOS::GetInstance()->RegisterProfilePrefsForServices(
pref_registry_.get());
// Create a SupervisedUserPrefStore and initialize it with empty data.
// The pref store will load SupervisedUserSettingsService disk data after
// the creation of PrefService.
scoped_refptr<SupervisedUserPrefStore> supervised_user_prefs =
base::MakeRefCounted<SupervisedUserPrefStore>();
supervised_user_prefs->OnNewSettingsAvailable(base::Value::Dict());
DCHECK(supervised_user_prefs->IsInitializationComplete());
prefs_ = CreateProfilePrefs(
state_path, GetIOTaskRunner().get(), pref_registry_,
policy_connector_ ? policy_connector_->GetPolicyService() : nullptr,
GetApplicationContext()->GetBrowserPolicyConnector(),
supervised_user_prefs, creation_mode == CreationMode::kAsynchronous);
const InitInfo init_info{
.creation_mode = creation_mode,
.is_new_profile = directories_creation_result.created,
.cache_path = directories_creation_result.cache_path,
.supervised_user_prefs = supervised_user_prefs,
};
if (init_info.creation_mode == CreationMode::kAsynchronous) {
// It is safe to use base::Unretained(...) here since `this` owns the
// PrefService and the callback will not be invoked after destruction
// of the PrefService.
prefs_->AddPrefInitObserver(base::BindOnce(
&ProfileIOSImpl::PrefsInitStage1, base::Unretained(this), init_info));
return;
}
// Either the operation was synchronous or unnecessary, move to the
// next stage of the profile initialisation.
PrefsInitStage1(init_info, true);
}
ProfileIOSImpl::~ProfileIOSImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Notify the callback of the profile destruction before destroying anything.
NotifyProfileDestroyed();
if (base::FeatureList::IsEnabled(kDestroyOTRProfileEarly)) {
// Destroy OTR profile first.
DestroyOffTheRecordProfile();
}
ProfileDependencyManagerIOS::GetInstance()->DestroyProfileServices(this);
// Warning: the order for shutting down the Profile's objects is important
// because of interdependencies. Ideally the order for shutting down the
// objects should be backward of their declaration in class attributes.
if (pref_proxy_config_tracker_) {
pref_proxy_config_tracker_->DetachFromPrefService();
}
// Here, (1) the profile services may depend on `policy_connector_` and
// `user_cloud_policy_manager_`, and (2) `policy_connector_` depends on
// `user_cloud_policy_manager_`. The dependencies have to be shut down
// backward.
policy_connector_->Shutdown();
if (user_cloud_policy_manager_) {
user_cloud_policy_manager_->Shutdown();
}
if (!base::FeatureList::IsEnabled(kDestroyOTRProfileEarly)) {
DestroyOffTheRecordProfile();
}
}
ProfileIOS* ProfileIOSImpl::GetOriginalProfile() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return this;
}
ProfileIOS* ProfileIOSImpl::GetOffTheRecordProfile() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!otr_state_) {
otr_state_.reset(new OffTheRecordProfileIOSImpl(
GetIOTaskRunner(), this, GetOffTheRecordStatePath()));
}
return otr_state_.get();
}
bool ProfileIOSImpl::HasOffTheRecordProfile() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !!otr_state_;
}
void ProfileIOSImpl::DestroyOffTheRecordProfile() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Tear down OTR Profile with which it is associated.
otr_state_.reset();
}
ProfilePolicyConnector* ProfileIOSImpl::GetPolicyConnector() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return policy_connector_.get();
}
policy::UserCloudPolicyManager* ProfileIOSImpl::GetUserCloudPolicyManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return user_cloud_policy_manager_.get();
}
sync_preferences::PrefServiceSyncable* ProfileIOSImpl::GetSyncablePrefs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(prefs_); // Should explicitly be initialized.
return prefs_.get();
}
const sync_preferences::PrefServiceSyncable* ProfileIOSImpl::GetSyncablePrefs()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(prefs_); // Should explicitly be initialized.
return prefs_.get();
}
bool ProfileIOSImpl::IsOffTheRecord() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return false;
}
const base::Uuid& ProfileIOSImpl::GetWebKitStorageID() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return storage_uuid_;
}
void ProfileIOSImpl::SetOffTheRecordProfileIOS(
std::unique_ptr<ProfileIOS> otr_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!otr_state_);
otr_state_ = std::move(otr_state);
}
void ProfileIOSImpl::PrefsInitStage1(InitInfo init_info, bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Early return in case of failure to load the preferences.
if (!success) {
if (delegate_) {
delegate_->OnProfileCreationFinished(this, init_info.creation_mode,
init_info.is_new_profile,
/*success=*/false);
}
return;
}
// Register on Profile.
user_prefs::UserPrefs::Set(this, prefs_.get());
// The HostContentSettingsMap constructor expects the prefs to have
// already been loaded from disk before it is instantiated. One the
// other hand, the SupervisedUserPrefStore is needed as part of the
// PrefService creation (thus before the prefs are loaded from disk)
// and must be registered with the HostContentSettingsMap.
//
// When loading the pref synchronously, the read happens as part of
// the CreateProfilePrefs(...) function call, but when loading them
// asynchronously, it happened on a background thread. This caused
// the HostContentSettingsMap to be created before the data was read
// from disk, and resulted in invalid values.
//
// To ensure that the construction of the objects happens in the
// same relative order compared to reading the data from disk when
// for both synchronous and asynchronous initialisation, move all
// the logic for initialising SupervisedUserPrefStore in the method
// invoked when the data has been read from disk (PrefsInitStage1).
// Initialize the settings service and have the pref store subscribe to it.
supervised_user::SupervisedUserSettingsService* supervised_user_settings =
SupervisedUserSettingsServiceFactory::GetForProfile(this);
const base::FilePath& state_path = GetStatePath();
supervised_user_settings->Init(
state_path, GetIOTaskRunner(),
init_info.creation_mode == CreationMode::kSynchronous);
init_info.supervised_user_prefs->Init(supervised_user_settings,
/*content_filters_service=*/nullptr);
auto supervised_provider =
std::make_unique<supervised_user::SupervisedUserContentSettingsProvider>(
supervised_user_settings);
ios::HostContentSettingsMapFactory::GetForProfile(this)->RegisterProvider(
content_settings::ProviderType::kSupervisedProvider,
std::move(supervised_provider));
const int cache_max_size = 0;
base::FilePath cookie_path = state_path.Append(kIOSChromeCookieFilename);
// Make sure we initialize the io_data_ after everything else has been
// initialized that we might be reading from the IO thread.
io_data_->Init(cookie_path, init_info.cache_path, cache_max_size, state_path);
// If the initialisation is asynchronous, then we also need to wait for
// the SupervisedUserSettingsService to complete its initialisation, if
// is not yet complete.
if (init_info.creation_mode == CreationMode::kAsynchronous) {
if (!supervised_user_settings->IsReady()) {
// It is safe to use base::Unretained(...) here since `this` owns the
// SupervisedUserSettingsService and the callback will not be invoked
// after destruction of the SupervisedUserSettingsService.
supervised_user_settings->WaitUntilReadyToSync(
base::BindOnce(&ProfileIOSImpl::PrefsInitStage2,
base::Unretained(this), init_info, success));
return;
}
}
// Either the operation was synchronous or unnecessary, move to the
// next stage of the profile initialisation.
PrefsInitStage2(init_info, success);
}
void ProfileIOSImpl::PrefsInitStage2(InitInfo init_info, bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(success);
// Migrate the preferences, unless the profile has just been created.
if (!init_info.is_new_profile) {
MigrateObsoleteProfilePrefs(prefs_.get());
// TODO(crbug.com/369296278): Remove on 12/2025.
//
// MaybeMigrateSyncingUserToSignedIn(...) may perform disk IO which is
// not permitted if the Profile is loaded asynchronously.
if (init_info.creation_mode == CreationMode::kAsynchronous) {
browser_sync::MaybeMigrateSyncingUserToSignedInAsync(
GetStatePath(), GetPrefs(),
base::BindOnce(&ProfileIOSImpl::PrefsInitStage3,
weak_ptr_factory_.GetWeakPtr(), init_info, success));
return;
}
browser_sync::MaybeMigrateSyncingUserToSignedIn(GetStatePath(), GetPrefs());
}
// Either the operation was synchronous or unnecessary, move to the
// next stage of the profile initialisation.
PrefsInitStage3(init_info, success);
}
void ProfileIOSImpl::PrefsInitStage3(InitInfo init_info, bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(success);
// Initialize the WebKit storage identifier.
const bool is_new_profile = init_info.is_new_profile;
storage_uuid_ = GetStorageUUID(GetProfileName(), GetPrefs(), is_new_profile);
// DO NOT ADD ANY INITIALISATION AFTER THIS LINE.
// The initialisation of the ProfileIOS is now complete and the services
// can be safely created.
ProfileDependencyManagerIOS::GetInstance()->CreateProfileServices(this);
if (delegate_) {
delegate_->OnProfileCreationFinished(this, init_info.creation_mode,
init_info.is_new_profile,
/*success=*/true);
}
}
ProfileIOSIOData* ProfileIOSImpl::GetIOData() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return io_data_->io_data();
}
net::URLRequestContextGetter* ProfileIOSImpl::CreateRequestContext(
ProtocolHandlerMap* protocol_handlers) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ApplicationContext* application_context = GetApplicationContext();
return io_data_
->CreateMainRequestContextGetter(
protocol_handlers, application_context->GetLocalState(),
application_context->GetIOSChromeIOThread())
.get();
}
base::WeakPtr<ProfileIOS> ProfileIOSImpl::AsWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return weak_ptr_factory_.GetWeakPtr();
}
void ProfileIOSImpl::ClearNetworkingHistorySince(base::Time time,
base::OnceClosure completion) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
io_data_->ClearNetworkingHistorySince(time, std::move(completion));
}
PrefProxyConfigTracker* ProfileIOSImpl::GetProxyConfigTracker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!pref_proxy_config_tracker_) {
pref_proxy_config_tracker_ =
ProxyServiceFactory::CreatePrefProxyConfigTrackerOfProfile(
GetPrefs(), GetApplicationContext()->GetLocalState());
}
return pref_proxy_config_tracker_.get();
}