blob: f7b9f915e6dc0d2045c152afabf9661cf0d0d4ad [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crosapi/prefs_ash.h"
#include <string>
#include <utility>
#include "ash/constants/ash_pref_names.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_prefs.h"
#include "chromeos/crosapi/mojom/prefs.mojom.h"
#include "components/language/core/browser/pref_names.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
namespace crosapi {
namespace {
// List of all mojom::PrefPaths associated with profile prefs, and their
// corresponding paths in the prefstore. Initialized on first use.
const std::string& GetProfilePrefNameForPref(mojom::PrefPath path) {
static base::NoDestructor<std::map<mojom::PrefPath, std::string>>
profile_prefpath_to_name({
{mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled,
ash::prefs::kAccessibilitySpokenFeedbackEnabled},
{mojom::PrefPath::kGeolocationAllowed,
ash::prefs::kUserGeolocationAllowed},
{mojom::PrefPath::kQuickAnswersEnabled,
quick_answers::prefs::kQuickAnswersEnabled},
{mojom::PrefPath::kQuickAnswersConsentStatus,
quick_answers::prefs::kQuickAnswersConsentStatus},
{mojom::PrefPath::kQuickAnswersDefinitionEnabled,
quick_answers::prefs::kQuickAnswersDefinitionEnabled},
{mojom::PrefPath::kQuickAnswersTranslationEnabled,
quick_answers::prefs::kQuickAnswersTranslationEnabled},
{mojom::PrefPath::kQuickAnswersUnitConversionEnabled,
quick_answers::prefs::kQuickAnswersUnitConversionEnabled},
{mojom::PrefPath::kQuickAnswersNoticeImpressionCount,
quick_answers::prefs::kQuickAnswersNoticeImpressionCount},
{mojom::PrefPath::kQuickAnswersNoticeImpressionDuration,
quick_answers::prefs::kQuickAnswersNoticeImpressionDuration},
{mojom::PrefPath::kPreferredLanguages,
language::prefs::kPreferredLanguages},
{mojom::PrefPath::kApplicationLocale,
language::prefs::kApplicationLocale},
{mojom::PrefPath::kSharedStorage, prefs::kSharedStorage},
});
auto pref_name = profile_prefpath_to_name->find(path);
DCHECK(pref_name != profile_prefpath_to_name->end());
return pref_name->second;
}
// List of all mojom::PrefPaths associated with extension controlled prefs,
// and their corresponding paths in the prefstore. Initialized on first use.
const std::string& GetExtensionPrefNameForPref(mojom::PrefPath path) {
static base::NoDestructor<std::map<mojom::PrefPath, std::string>>
extension_prefpath_to_name(
{{mojom::PrefPath::kDockedMagnifierEnabled,
ash::prefs::kDockedMagnifierEnabled},
{mojom::PrefPath::kAccessibilityAutoclickEnabled,
ash::prefs::kAccessibilityAutoclickEnabled},
{mojom::PrefPath::kAccessibilityCaretHighlightEnabled,
ash::prefs::kAccessibilityCaretHighlightEnabled},
{mojom::PrefPath::kAccessibilityCursorColorEnabled,
ash::prefs::kAccessibilityCursorColorEnabled},
{mojom::PrefPath::kAccessibilityCursorHighlightEnabled,
ash::prefs::kAccessibilityCursorHighlightEnabled},
{mojom::PrefPath::kAccessibilityDictationEnabled,
ash::prefs::kAccessibilityDictationEnabled},
{mojom::PrefPath::kAccessibilityFocusHighlightEnabled,
ash::prefs::kAccessibilityFocusHighlightEnabled},
{mojom::PrefPath::kAccessibilityHighContrastEnabled,
ash::prefs::kAccessibilityHighContrastEnabled},
{mojom::PrefPath::kAccessibilityLargeCursorEnabled,
ash::prefs::kAccessibilityLargeCursorEnabled},
{mojom::PrefPath::kAccessibilityScreenMagnifierEnabled,
ash::prefs::kAccessibilityScreenMagnifierEnabled},
{mojom::PrefPath::kAccessibilitySelectToSpeakEnabled,
ash::prefs::kAccessibilitySelectToSpeakEnabled},
{mojom::PrefPath::kExtensionAccessibilitySpokenFeedbackEnabled,
ash::prefs::kAccessibilitySpokenFeedbackEnabled},
{mojom::PrefPath::kAccessibilityStickyKeysEnabled,
ash::prefs::kAccessibilityStickyKeysEnabled},
{mojom::PrefPath::kAccessibilitySwitchAccessEnabled,
ash::prefs::kAccessibilitySwitchAccessEnabled},
{mojom::PrefPath::kAccessibilityVirtualKeyboardEnabled,
ash::prefs::kAccessibilityVirtualKeyboardEnabled},
{mojom::PrefPath::kProtectedContentDefault,
prefs::kProtectedContentDefault}});
auto pref_name = extension_prefpath_to_name->find(path);
DCHECK(pref_name != extension_prefpath_to_name->end());
return pref_name->second;
}
} // namespace
PrefsAsh::PrefsAsh(ProfileManager* profile_manager, PrefService* local_state)
: local_state_(local_state) {
DCHECK(profile_manager);
DCHECK(local_state_);
on_app_terminating_subscription_ =
browser_shutdown::AddAppTerminatingCallback(
base::BindOnce(&PrefsAsh::OnAppTerminating, base::Unretained(this)));
profile_manager_observation_.Observe(profile_manager);
local_state_registrar_.Init(local_state_);
}
PrefsAsh::~PrefsAsh() = default;
void PrefsAsh::BindReceiver(mojo::PendingReceiver<mojom::Prefs> receiver) {
receivers_.Add(this, std::move(receiver));
}
void PrefsAsh::GetPref(mojom::PrefPath path, GetPrefCallback callback) {
auto state = GetState(path);
const base::Value* value =
state ? &state->pref_service->GetValue(state->path) : nullptr;
std::move(callback).Run(value ? absl::optional<base::Value>(value->Clone())
: absl::nullopt);
}
void PrefsAsh::GetExtensionPrefWithControl(
mojom::PrefPath path,
GetExtensionPrefWithControlCallback callback) {
auto state = GetState(path);
if (!state) {
// Not a valid prefpath
std::move(callback).Run(absl::nullopt,
mojom::PrefControlState::kDefaultUnknown);
return;
}
const base::Value& value = state->pref_service->GetValue(state->path);
if (!state->is_extension_controlled_pref) {
// Not extension controlled
std::move(callback).Run(
absl::optional<base::Value>(value.Clone()),
mojom::PrefControlState::kNotExtensionControlledPrefPath);
return;
}
mojom::PrefControlState pref_control_state;
// Extension controlled.
const PrefService::Preference* preference =
state->pref_service->FindPreference(state->path);
DCHECK(preference != nullptr);
if (!preference->IsStandaloneBrowserModifiable()) {
pref_control_state = mojom::PrefControlState::kNotExtensionControllable;
} else if (preference->IsStandaloneBrowserControlled()) {
// Lacros has already set this pref. It could be set by any extension
// in lacros.
pref_control_state = mojom::PrefControlState::kLacrosExtensionControlled;
} else {
// Lacros could control this.
pref_control_state = mojom::PrefControlState::kLacrosExtensionControllable;
}
std::move(callback).Run(absl::optional<base::Value>(value.Clone()),
pref_control_state);
}
void PrefsAsh::SetPref(mojom::PrefPath path,
base::Value value,
SetPrefCallback callback) {
auto state = GetState(path);
if (state) {
if (state->is_extension_controlled_pref) {
state->pref_service->SetStandaloneBrowserPref(state->path, value);
} else {
state->pref_service->Set(state->path, value);
}
}
std::move(callback).Run();
}
void PrefsAsh::ClearExtensionControlledPref(
mojom::PrefPath path,
ClearExtensionControlledPrefCallback callback) {
auto state = GetState(path);
if (state && state->is_extension_controlled_pref) {
state->pref_service->RemoveStandaloneBrowserPref(state->path);
} else {
// Only logging to be robust against version skew (lacros ahead of ash)
LOG(WARNING) << "Tried to clear a pref that is not extension controlled";
}
std::move(callback).Run();
}
void PrefsAsh::AddObserver(mojom::PrefPath path,
mojo::PendingRemote<mojom::PrefObserver> observer) {
auto state = GetState(path);
const base::Value* value =
state ? &state->pref_service->GetValue(state->path) : nullptr;
if (!value) {
return;
}
// Fire the observer with the initial value.
mojo::Remote<mojom::PrefObserver> remote(std::move(observer));
remote->OnPrefChanged(value->Clone());
DCHECK(state->registrar);
if (!state->registrar->IsObserved(state->path)) {
// Unretained() is safe since PrefChangeRegistrar and RemoteSet within
// observers_ are owned by this and wont invoke if PrefsAsh is destroyed.
state->registrar->Add(state->path,
base::BindRepeating(&PrefsAsh::OnPrefChanged,
base::Unretained(this), path));
observers_[path].set_disconnect_handler(base::BindRepeating(
&PrefsAsh::OnDisconnect, base::Unretained(this), path));
}
observers_[path].Add(std::move(remote));
}
void PrefsAsh::OnProfileAdded(Profile* profile) {
if (!ash::ProfileHelper::IsPrimaryProfile(profile)) {
return;
}
OnPrimaryProfileReady(profile);
}
absl::optional<PrefsAsh::State> PrefsAsh::GetState(mojom::PrefPath path) {
switch (path) {
case mojom::PrefPath::kUnknown:
LOG(WARNING) << "Unknown pref path: " << path;
return absl::nullopt;
case mojom::PrefPath::kMetricsReportingEnabled:
return State{local_state_, &local_state_registrar_, false,
metrics::prefs::kMetricsReportingEnabled};
case mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled:
case mojom::PrefPath::kGeolocationAllowed:
case mojom::PrefPath::kQuickAnswersEnabled:
case mojom::PrefPath::kQuickAnswersConsentStatus:
case mojom::PrefPath::kQuickAnswersDefinitionEnabled:
case mojom::PrefPath::kQuickAnswersTranslationEnabled:
case mojom::PrefPath::kQuickAnswersUnitConversionEnabled:
case mojom::PrefPath::kQuickAnswersNoticeImpressionCount:
case mojom::PrefPath::kQuickAnswersNoticeImpressionDuration:
case mojom::PrefPath::kPreferredLanguages:
case mojom::PrefPath::kApplicationLocale:
case mojom::PrefPath::kSharedStorage: {
if (!profile_prefs_registrar_) {
LOG(WARNING) << "Primary profile is not yet initialized";
return absl::nullopt;
}
std::string pref_name = GetProfilePrefNameForPref(path);
return State{profile_prefs_registrar_->prefs(),
profile_prefs_registrar_.get(), false, pref_name};
}
case mojom::PrefPath::kDeviceSystemWideTracingEnabled:
return State{local_state_, &local_state_registrar_, false,
ash::prefs::kDeviceSystemWideTracingEnabled};
case mojom::PrefPath::kDnsOverHttpsMode:
return State{local_state_, &local_state_registrar_, false,
prefs::kDnsOverHttpsMode};
case mojom::PrefPath::kDnsOverHttpsSalt:
return State{local_state_, &local_state_registrar_, false,
prefs::kDnsOverHttpsSalt};
case mojom::PrefPath::kDnsOverHttpsTemplates:
return State{local_state_, &local_state_registrar_, false,
prefs::kDnsOverHttpsTemplates};
case mojom::PrefPath::kDnsOverHttpsTemplatesWithIdentifiers:
return State{local_state_, &local_state_registrar_, false,
prefs::kDnsOverHttpsTemplatesWithIdentifiers};
case mojom::PrefPath::kDockedMagnifierEnabled:
case mojom::PrefPath::kAccessibilityAutoclickEnabled:
case mojom::PrefPath::kAccessibilityCaretHighlightEnabled:
case mojom::PrefPath::kAccessibilityCursorColorEnabled:
case mojom::PrefPath::kAccessibilityCursorHighlightEnabled:
case mojom::PrefPath::kAccessibilityDictationEnabled:
case mojom::PrefPath::kAccessibilityFocusHighlightEnabled:
case mojom::PrefPath::kAccessibilityHighContrastEnabled:
case mojom::PrefPath::kAccessibilityLargeCursorEnabled:
case mojom::PrefPath::kAccessibilityScreenMagnifierEnabled:
case mojom::PrefPath::kAccessibilitySelectToSpeakEnabled:
case mojom::PrefPath::kExtensionAccessibilitySpokenFeedbackEnabled:
case mojom::PrefPath::kAccessibilityStickyKeysEnabled:
case mojom::PrefPath::kAccessibilitySwitchAccessEnabled:
case mojom::PrefPath::kAccessibilityVirtualKeyboardEnabled:
case mojom::PrefPath::kProtectedContentDefault: {
if (!profile_prefs_registrar_) {
LOG(WARNING) << "Primary profile is not yet initialized";
return absl::nullopt;
}
std::string pref_name = GetExtensionPrefNameForPref(path);
return State{profile_prefs_registrar_->prefs(),
profile_prefs_registrar_.get(), true, pref_name};
}
}
}
void PrefsAsh::OnProfileManagerDestroying() {
profile_manager_observation_.Reset();
}
void PrefsAsh::OnProfileWillBeDestroyed(Profile* profile) {
profile_observation_.Reset();
profile_prefs_registrar_.reset();
}
void PrefsAsh::OnPrefChanged(mojom::PrefPath path) {
auto state = GetState(path);
const base::Value* value =
state ? &state->pref_service->GetValue(state->path) : nullptr;
if (value) {
for (auto& observer : observers_[path]) {
observer->OnPrefChanged(value->Clone());
}
}
}
void PrefsAsh::OnDisconnect(mojom::PrefPath path, mojo::RemoteSetElementId id) {
const auto& it = observers_.find(path);
if (it != observers_.end() && it->second.empty()) {
if (auto state = GetState(path)) {
state->registrar->Remove(state->path);
}
observers_.erase(it);
}
}
void PrefsAsh::OnPrimaryProfileReady(Profile* profile) {
profile_manager_observation_.Reset();
profile_prefs_registrar_ = std::make_unique<PrefChangeRegistrar>();
profile_prefs_registrar_->Init(profile->GetPrefs());
}
void PrefsAsh::OnAppTerminating() {
profile_prefs_registrar_.reset();
}
} // namespace crosapi