| // 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 <string_view> |
| #include <utility> |
| |
| #include "ash/constants/ash_pref_names.h" |
| #include "base/check.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/functional/bind.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/lifetime/termination_notification.h" |
| #include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #include "chromeos/components/quick_answers/public/cpp/quick_answers_prefs.h" |
| #include "chromeos/crosapi/mojom/prefs.mojom.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/language/core/browser/pref_names.h" |
| #include "components/metrics/metrics_pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/proxy_config/proxy_config_pref_names.h" |
| #include "components/search_engines/default_search_manager.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. |
| std::string_view GetProfilePrefNameForPref(mojom::PrefPath path) { |
| static constexpr auto kProfilePrefPathToName = |
| base::MakeFixedFlatMap<mojom::PrefPath, std::string_view>({ |
| {mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled, |
| ash::prefs::kAccessibilitySpokenFeedbackEnabled}, |
| {mojom::PrefPath::kAccessibilityReducedAnimationsEnabled, |
| ash::prefs::kAccessibilityReducedAnimationsEnabled}, |
| {mojom::PrefPath::kUserGeolocationAccessLevel, |
| ash::prefs::kUserGeolocationAccessLevel}, |
| {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}, |
| {mojom::PrefPath::kMultitaskMenuNudgeClamshellShownCount, |
| ash::prefs::kMultitaskMenuNudgeClamshellShownCount}, |
| {mojom::PrefPath::kMultitaskMenuNudgeClamshellLastShown, |
| ash::prefs::kMultitaskMenuNudgeClamshellLastShown}, |
| {mojom::PrefPath::kAccessCodeCastDevices, |
| media_router::prefs::kAccessCodeCastDevices}, |
| {mojom::PrefPath::kAccessCodeCastDeviceAdditionTime, |
| media_router::prefs::kAccessCodeCastDeviceAdditionTime}, |
| {mojom::PrefPath::kDefaultSearchProviderDataPrefName, |
| DefaultSearchManager::kDefaultSearchProviderDataPrefName}, |
| {mojom::PrefPath::kIsolatedWebAppsEnabled, |
| ash::prefs::kIsolatedWebAppsEnabled}, |
| {mojom::PrefPath::kHmrEnabled, ash::prefs::kHmrEnabled}, |
| {mojom::PrefPath::kUserCameraAllowed, ash::prefs::kUserCameraAllowed}, |
| {mojom::PrefPath::kUserMicrophoneAllowed, |
| ash::prefs::kUserMicrophoneAllowed}, |
| {mojom::PrefPath::kHMRConsentStatus, ash::prefs::kHMRConsentStatus}, |
| {mojom::PrefPath::kHMRConsentWindowDismissCount, |
| ash::prefs::kHMRConsentWindowDismissCount}, |
| |
| }); |
| auto pref_name = kProfilePrefPathToName.find(path); |
| DCHECK(pref_name != kProfilePrefPathToName.end()); |
| return pref_name->second; |
| } |
| |
| // List of all mojom::PrefPaths associated with extension controlled prefs, |
| // and their corresponding paths in the prefstore. |
| std::string_view GetExtensionPrefNameForPref(mojom::PrefPath path) { |
| static constexpr auto kExtensionPrefPathToName = |
| base::MakeFixedFlatMap<mojom::PrefPath, std::string_view>( |
| {{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::kProxy, ash::prefs::kProxy}}); |
| auto pref_name = kExtensionPrefPathToName.find(path); |
| DCHECK(pref_name != kExtensionPrefPathToName.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) { |
| const base::Value* value = GetValueForState(GetState(path)); |
| std::move(callback).Run(value ? std::optional<base::Value>(value->Clone()) |
| : std::nullopt); |
| } |
| |
| void PrefsAsh::GetExtensionPrefWithControl( |
| mojom::PrefPath path, |
| GetExtensionPrefWithControlCallback callback) { |
| auto state = GetState(path); |
| const base::Value* value = GetValueForState(state); |
| |
| if (!state || !value) { |
| // Not a valid prefpath |
| std::move(callback).Run(std::nullopt, |
| mojom::PrefControlState::kDefaultUnknown); |
| return; |
| } |
| |
| if (state->pref_source != AshPrefSource::kExtensionControlled) { |
| // Not extension controlled |
| std::move(callback).Run( |
| std::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(std::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 && state->pref_source != AshPrefSource::kCrosSettings) { |
| if (state->pref_source == AshPrefSource::kExtensionControlled) { |
| state->pref_service->SetStandaloneBrowserPref(state->path, value); |
| } else { |
| state->pref_service->Set(state->path, value); |
| } |
| } else { |
| LOG(WARNING) << "CrosSettings can't be changed via PrefsAsh"; |
| } |
| std::move(callback).Run(); |
| } |
| |
| void PrefsAsh::ClearExtensionControlledPref( |
| mojom::PrefPath path, |
| ClearExtensionControlledPrefCallback callback) { |
| auto state = GetState(path); |
| if (state && state->pref_source == AshPrefSource::kExtensionControlled) { |
| 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 = GetValueForState(state); |
| if (!value) { |
| return; |
| } |
| |
| // Fire the observer with the initial value. |
| mojo::Remote<mojom::PrefObserver> remote(std::move(observer)); |
| remote->OnPrefChanged(value->Clone()); |
| |
| bool did_register = false; |
| if (state->pref_source == AshPrefSource::kCrosSettings) { |
| if (cros_settings_subs_.find(path) == cros_settings_subs_.end()) { |
| // Unretained() is safe since CrosSettings is destroyed after all the |
| // threads are stopped and PrefsAsh is destroyed while stopping all the |
| // threads. |
| cros_settings_subs_.emplace( |
| path, |
| ash::CrosSettings::Get()->AddSettingsObserver( |
| state->path, base::BindRepeating(&PrefsAsh::OnPrefChanged, |
| base::Unretained(this), path))); |
| did_register = true; |
| } |
| } else { |
| 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)); |
| did_register = true; |
| } |
| } |
| if (did_register) { |
| 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); |
| } |
| |
| std::optional<PrefsAsh::State> PrefsAsh::GetState(mojom::PrefPath path) { |
| switch (path) { |
| case mojom::PrefPath::kUnknown: |
| case mojom::PrefPath::kProtectedContentDefaultDeprecated: |
| case mojom::PrefPath::kDnsOverHttpsTemplates: |
| case mojom::PrefPath::kDnsOverHttpsTemplatesWithIdentifiers: |
| case mojom::PrefPath::kDnsOverHttpsSalt: |
| case mojom::PrefPath::kAccessibilityPdfOcrAlwaysActiveDeprecated: |
| case mojom::PrefPath::kMahiEnabledDeprecated: |
| LOG(WARNING) << "Unknown pref path: " << path; |
| return std::nullopt; |
| case mojom::PrefPath::kMetricsReportingEnabled: |
| return State{local_state_, &local_state_registrar_, |
| AshPrefSource::kNormal, |
| metrics::prefs::kMetricsReportingEnabled}; |
| case mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled: |
| case mojom::PrefPath::kUserGeolocationAccessLevel: |
| 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: |
| case mojom::PrefPath::kMultitaskMenuNudgeClamshellShownCount: |
| case mojom::PrefPath::kMultitaskMenuNudgeClamshellLastShown: |
| case mojom::PrefPath::kAccessCodeCastDevices: |
| case mojom::PrefPath::kAccessCodeCastDeviceAdditionTime: |
| case mojom::PrefPath::kDefaultSearchProviderDataPrefName: |
| case mojom::PrefPath::kIsolatedWebAppsEnabled: |
| case mojom::PrefPath::kAccessibilityReducedAnimationsEnabled: |
| case mojom::PrefPath::kHmrEnabled: |
| case mojom::PrefPath::kUserCameraAllowed: |
| case mojom::PrefPath::kUserMicrophoneAllowed: |
| case mojom::PrefPath::kHMRConsentStatus: |
| case mojom::PrefPath::kHMRConsentWindowDismissCount: { |
| if (!profile_prefs_registrar_) { |
| LOG(WARNING) << "Primary profile is not yet initialized"; |
| return std::nullopt; |
| } |
| std::string pref_name(GetProfilePrefNameForPref(path)); |
| return State{profile_prefs_registrar_->prefs(), |
| profile_prefs_registrar_.get(), AshPrefSource::kNormal, |
| pref_name}; |
| } |
| case mojom::PrefPath::kDeviceSystemWideTracingEnabled: |
| return State{local_state_, &local_state_registrar_, |
| AshPrefSource::kNormal, |
| ash::prefs::kDeviceSystemWideTracingEnabled}; |
| case mojom::PrefPath::kDnsOverHttpsMode: |
| return State{local_state_, &local_state_registrar_, |
| AshPrefSource::kNormal, prefs::kDnsOverHttpsMode}; |
| case mojom::PrefPath::kDnsOverHttpsEffectiveTemplatesChromeOS: |
| return State{local_state_, &local_state_registrar_, |
| AshPrefSource::kNormal, |
| prefs::kDnsOverHttpsEffectiveTemplatesChromeOS}; |
| case mojom::PrefPath::kOverscrollHistoryNavigationEnabled: |
| return State{local_state_, &local_state_registrar_, |
| AshPrefSource::kNormal, |
| prefs::kOverscrollHistoryNavigationEnabled}; |
| 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::kProxy: { |
| if (!profile_prefs_registrar_) { |
| LOG(WARNING) << "Primary profile is not yet initialized"; |
| return std::nullopt; |
| } |
| std::string pref_name(GetExtensionPrefNameForPref(path)); |
| return State{profile_prefs_registrar_->prefs(), |
| profile_prefs_registrar_.get(), |
| AshPrefSource::kExtensionControlled, pref_name}; |
| } |
| case mojom::PrefPath::kAttestationForContentProtectionEnabled: { |
| return State{nullptr, nullptr, AshPrefSource::kCrosSettings, |
| ash::kAttestationForContentProtectionEnabled}; |
| } |
| case mojom::PrefPath::kAccessToGetAllScreensMediaInSessionAllowedForUrls: |
| if (!profile_prefs_registrar_) { |
| LOG(WARNING) << "Primary profile is not yet initialized"; |
| return std::nullopt; |
| } |
| return State{ |
| .pref_service = profile_prefs_registrar_->prefs(), |
| .registrar = profile_prefs_registrar_.get(), |
| .pref_source = AshPrefSource::kNormal, |
| .path = |
| prefs::kManagedAccessToGetAllScreensMediaInSessionAllowedForUrls}; |
| } |
| } |
| |
| const base::Value* PrefsAsh::GetValueForState(std::optional<State> state) { |
| if (!state) { |
| return nullptr; |
| } |
| |
| if (state->pref_source == AshPrefSource::kCrosSettings) { |
| return ash::CrosSettings::Get()->GetPref(state->path); |
| } |
| |
| return &state->pref_service->GetValue(state->path); |
| } |
| |
| 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 = GetValueForState(state); |
| 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)) { |
| if (state->pref_source == AshPrefSource::kCrosSettings) { |
| cros_settings_subs_.erase(path); |
| } else { |
| 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 |