From 549e8fe05c06941ee25e06fc0a9ff42f2f701a9b Mon Sep 17 00:00:00 2001
From: Marshall Greenblatt
Date: Mon, 10 Mar 2025 15:50:46 +0000
Subject: [PATCH] Add visualization for Chrome configuration changes (fixes
#3892)
- Add new API to retrieve/observe configuration values.
- cefclient: Add https://tests/config to inspect configuration
values in real time.
---
BUILD.gn | 2 +
cef_paths2.gypi | 3 +
include/cef_preference.h | 64 ++
include/cef_request_context.h | 33 +
libcef/browser/context.cc | 25 +-
libcef/browser/context.h | 6 +
.../browser/global_preference_manager_impl.cc | 89 +++
.../browser/global_preference_manager_impl.h | 3 +
libcef/browser/prefs/pref_helper.cc | 142 ++++
libcef/browser/prefs/pref_helper.h | 73 +-
libcef/browser/request_context_impl.cc | 38 +
libcef/browser/request_context_impl.h | 35 +-
libcef/browser/setting_helper.cc | 118 +++
libcef/browser/setting_helper.h | 81 ++
patch/patch.cfg | 9 +
patch/patches/config_3892.patch | 85 +++
tests/cefclient/browser/config_test.cc | 334 +++++++++
tests/cefclient/browser/config_test.h | 18 +
tests/cefclient/browser/resource.h | 53 +-
.../browser/resource_util_win_idmap.cc | 1 +
tests/cefclient/browser/test_runner.cc | 4 +
tests/cefclient/resources/config.html | 703 ++++++++++++++++++
tests/cefclient/resources/other_tests.html | 1 +
tests/cefclient/resources/preferences.html | 6 +-
tests/cefclient/win/cefclient.rc | 1 +
25 files changed, 1887 insertions(+), 40 deletions(-)
create mode 100644 libcef/browser/setting_helper.cc
create mode 100644 libcef/browser/setting_helper.h
create mode 100644 patch/patches/config_3892.patch
create mode 100644 tests/cefclient/browser/config_test.cc
create mode 100644 tests/cefclient/browser/config_test.h
create mode 100644 tests/cefclient/resources/config.html
diff --git a/BUILD.gn b/BUILD.gn
index 199128ba9..8dcd235c7 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -677,6 +677,8 @@ source_set("libcef_static") {
"libcef/browser/scheme_impl.cc",
"libcef/browser/server_impl.cc",
"libcef/browser/server_impl.h",
+ "libcef/browser/setting_helper.cc",
+ "libcef/browser/setting_helper.h",
"libcef/browser/simple_menu_model_impl.cc",
"libcef/browser/simple_menu_model_impl.h",
"libcef/browser/ssl_info_impl.cc",
diff --git a/cef_paths2.gypi b/cef_paths2.gypi
index d923dec71..41be58245 100644
--- a/cef_paths2.gypi
+++ b/cef_paths2.gypi
@@ -247,6 +247,8 @@
'tests/cefclient/browser/client_prefs.cc',
'tests/cefclient/browser/client_prefs.h',
'tests/cefclient/browser/client_types.h',
+ 'tests/cefclient/browser/config_test.cc',
+ 'tests/cefclient/browser/config_test.h',
'tests/cefclient/browser/default_client_handler.cc',
'tests/cefclient/browser/default_client_handler.h',
'tests/cefclient/browser/dialog_test.cc',
@@ -324,6 +326,7 @@
'cefclient_sources_resources': [
'tests/cefclient/resources/binary_transfer.html',
'tests/cefclient/resources/binding.html',
+ 'tests/cefclient/resources/config.html',
'tests/cefclient/resources/dialogs.html',
'tests/cefclient/resources/draggable.html',
'tests/cefclient/resources/hang.html',
diff --git a/include/cef_preference.h b/include/cef_preference.h
index 8f3d61afd..348b6320b 100644
--- a/include/cef_preference.h
+++ b/include/cef_preference.h
@@ -41,6 +41,7 @@
#include
#include "include/cef_base.h"
+#include "include/cef_registration.h"
#include "include/cef_values.h"
///
@@ -65,6 +66,24 @@ class CefPreferenceRegistrar : public CefBaseScoped {
CefRefPtr default_value) = 0;
};
+#if CEF_API_ADDED(CEF_NEXT)
+///
+/// Implemented by the client to observe preference changes and registered via
+/// CefPreferenceManager::AddPreferenceObserver. The methods of this class will
+/// be called on the browser process UI thread.
+///
+/*--cef(source=client,added=next)--*/
+class CefPreferenceObserver : public virtual CefBaseRefCounted {
+ public:
+ ///
+ /// Called when a preference has changed. The new value can be retrieved using
+ /// CefPreferenceManager::GetPreference.
+ ///
+ /*--cef()--*/
+ virtual void OnPreferenceChanged(const CefString& name) = 0;
+};
+#endif
+
///
/// Manage access to preferences. Many built-in preferences are registered by
/// Chromium. Custom preferences can be registered in
@@ -73,6 +92,36 @@ class CefPreferenceRegistrar : public CefBaseScoped {
/*--cef(source=library,no_debugct_check)--*/
class CefPreferenceManager : public virtual CefBaseRefCounted {
public:
+#if CEF_API_ADDED(CEF_NEXT)
+ ///
+ /// Returns the current Chrome Variations configuration (combination of field
+ /// trials and chrome://flags) as equivalent command-line switches
+ /// (`--[enable|disable]-features=XXXX`, etc). These switches can be used to
+ /// apply the same configuration when launching a CEF-based application. See
+ /// https://developer.chrome.com/docs/web-platform/chrome-variations for
+ /// background and details. Note that field trial tests are disabled by
+ /// default in Official CEF builds (via the
+ /// `disable_fieldtrial_testing_config=true` GN flag). This method must be
+ /// called on the browser process UI thread.
+ ///
+ /*--cef(added=next)--*/
+ static void GetChromeVariationsAsSwitches(std::vector& switches);
+
+ ///
+ /// Returns the current Chrome Variations configuration (combination of field
+ /// trials and chrome://flags) as human-readable strings. This is the
+ /// human-readable equivalent of the "Active Variations" section of
+ /// chrome://version. See
+ /// https://developer.chrome.com/docs/web-platform/chrome-variations for
+ /// background and details. Note that field trial tests are disabled by
+ /// default in Official CEF builds (via the
+ /// `disable_fieldtrial_testing_config=true` GN flag). This method must be
+ /// called on the browser process UI thread.
+ ///
+ /*--cef(added=next)--*/
+ static void GetChromeVariationsAsStrings(std::vector& strings);
+#endif
+
///
/// Returns the global preference manager object.
///
@@ -129,6 +178,21 @@ class CefPreferenceManager : public virtual CefBaseRefCounted {
virtual bool SetPreference(const CefString& name,
CefRefPtr value,
CefString& error) = 0;
+
+#if CEF_API_ADDED(CEF_NEXT)
+ ///
+ /// Add an observer for preference changes. |name| is the name of the
+ /// preference to observe. If |name| is empty then all preferences will
+ /// be observed. Observing all preferences has performance consequences and
+ /// is not recommended outside of testing scenarios. The observer will remain
+ /// registered until the returned Registration object is destroyed. This
+ /// method must be called on the browser process UI thread.
+ ///
+ /*--cef(optional_param=name,added=next)--*/
+ virtual CefRefPtr AddPreferenceObserver(
+ const CefString& name,
+ CefRefPtr observer) = 0;
+#endif
};
#endif // CEF_INCLUDE_CEF_PREFERENCE_H_
diff --git a/include/cef_request_context.h b/include/cef_request_context.h
index 56d3610bd..406649702 100644
--- a/include/cef_request_context.h
+++ b/include/cef_request_context.h
@@ -44,6 +44,7 @@
#include "include/cef_cookie.h"
#include "include/cef_media_router.h"
#include "include/cef_preference.h"
+#include "include/cef_registration.h"
#include "include/cef_values.h"
class CefRequestContextHandler;
@@ -66,6 +67,27 @@ class CefResolveCallback : public virtual CefBaseRefCounted {
const std::vector& resolved_ips) = 0;
};
+#if CEF_API_ADDED(CEF_NEXT)
+///
+/// Implemented by the client to observe content and website setting changes and
+/// registered via CefRequestContext::AddSettingObserver. The methods of this
+/// class will be called on the browser process UI thread.
+///
+/*--cef(source=client,added=next)--*/
+class CefSettingObserver : public virtual CefBaseRefCounted {
+ public:
+ ///
+ /// Called when a content or website setting has changed. The new value can be
+ /// retrieved using CefRequestContext::GetContentSetting or
+ /// CefRequestContext::GetWebsiteSetting.
+ ///
+ /*--cef(optional_param=requesting_url,optional_param=top_level_url)--*/
+ virtual void OnSettingChanged(const CefString& requesting_url,
+ const CefString& top_level_url,
+ cef_content_setting_types_t content_type) = 0;
+};
+#endif
+
///
/// A request context provides request handling for a set of related browser
/// or URL request objects. A request context can be specified when creating a
@@ -292,6 +314,17 @@ class CefRequestContext : public CefPreferenceManager {
cef_content_setting_types_t content_type,
cef_content_setting_values_t value) = 0;
+#if CEF_API_ADDED(CEF_NEXT)
+ ///
+ /// Add an observer for content and website setting changes. The observer will
+ /// remain registered until the returned Registration object is destroyed.
+ /// This method must be called on the browser process UI thread.
+ ///
+ /*--cef(added=next)--*/
+ virtual CefRefPtr AddSettingObserver(
+ CefRefPtr observer) = 0;
+#endif
+
///
/// Sets the Chrome color scheme for all browsers that share this request
/// context. |variant| values of SYSTEM, LIGHT and DARK change the underlying
diff --git a/libcef/browser/context.cc b/libcef/browser/context.cc
index 2c16b72d2..fc1abef02 100644
--- a/libcef/browser/context.cc
+++ b/libcef/browser/context.cc
@@ -12,10 +12,12 @@
#include "base/task/current_thread.h"
#include "base/threading/thread_restrictions.h"
#include "cef/libcef/browser/browser_info_manager.h"
+#include "cef/libcef/browser/prefs/pref_helper.h"
#include "cef/libcef/browser/request_context_impl.h"
#include "cef/libcef/browser/thread_util.h"
#include "cef/libcef/browser/trace_subscriber.h"
#include "cef/libcef/common/cef_switches.h"
+#include "chrome/browser/browser_process_impl.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "ui/base/ui_base_switches.h"
@@ -570,12 +572,24 @@ CefTraceSubscriber* CefContext::GetTraceSubscriber() {
if (shutting_down_) {
return nullptr;
}
- if (!trace_subscriber_.get()) {
+ if (!trace_subscriber_) {
trace_subscriber_ = std::make_unique();
}
return trace_subscriber_.get();
}
+pref_helper::Registrar* CefContext::GetPrefRegistrar() {
+ CEF_REQUIRE_UIT();
+ if (shutting_down_) {
+ return nullptr;
+ }
+ if (!pref_registrar_) {
+ pref_registrar_ = std::make_unique();
+ pref_registrar_->Init(g_browser_process->local_state());
+ }
+ return pref_registrar_.get();
+}
+
void CefContext::PopulateGlobalRequestContextSettings(
CefRequestContextSettings* settings) {
CefRefPtr command_line =
@@ -645,12 +659,15 @@ void CefContext::ShutdownOnUIThread() {
observer.OnContextDestroyed();
}
- if (trace_subscriber_.get()) {
- trace_subscriber_.reset(nullptr);
+ if (trace_subscriber_) {
+ trace_subscriber_.reset();
+ }
+ if (pref_registrar_) {
+ pref_registrar_.reset();
}
}
void CefContext::FinalizeShutdown() {
- browser_info_manager_.reset(nullptr);
+ browser_info_manager_.reset();
application_ = nullptr;
}
diff --git a/libcef/browser/context.h b/libcef/browser/context.h
index d62853dec..a9aec0a85 100644
--- a/libcef/browser/context.h
+++ b/libcef/browser/context.h
@@ -16,6 +16,10 @@
#include "cef/libcef/browser/main_runner.h"
#include "third_party/skia/include/core/SkColor.h"
+namespace pref_helper {
+class Registrar;
+}
+
class CefBrowserInfoManager;
class CefTraceSubscriber;
@@ -73,6 +77,7 @@ class CefContext {
cef_state_t windowless_state) const;
CefTraceSubscriber* GetTraceSubscriber();
+ pref_helper::Registrar* GetPrefRegistrar();
// Populate request context settings for the global system context based on
// CefSettings and command-line flags.
@@ -112,6 +117,7 @@ class CefContext {
std::unique_ptr main_runner_;
std::unique_ptr trace_subscriber_;
+ std::unique_ptr pref_registrar_;
std::unique_ptr browser_info_manager_;
// Observers that want to be notified of changes to this object.
diff --git a/libcef/browser/global_preference_manager_impl.cc b/libcef/browser/global_preference_manager_impl.cc
index 9a718c6b1..1a3a0a18f 100644
--- a/libcef/browser/global_preference_manager_impl.cc
+++ b/libcef/browser/global_preference_manager_impl.cc
@@ -4,10 +4,28 @@
#include "cef/libcef/browser/global_preference_manager_impl.h"
+#include "base/metrics/field_trial_list_including_low_anonymity.h"
+#include "base/strings/string_util.h"
#include "cef/libcef/browser/context.h"
#include "cef/libcef/browser/prefs/pref_helper.h"
#include "cef/libcef/browser/thread_util.h"
+#include "cef/libcef/common/api_version_util.h"
+#include "chrome/browser/about_flags.h"
#include "chrome/browser/browser_process.h"
+#include "components/flags_ui/pref_service_flags_storage.h"
+#include "components/variations/synthetic_trials_active_group_id_provider.h"
+
+namespace {
+
+std::string GetActiveGroupNameAsString(
+ const base::FieldTrial::ActiveGroup& group) {
+ constexpr std::string_view kNonBreakingHyphenUTF8 = "\xE2\x80\x91";
+ std::string result = group.trial_name + ":" + group.group_name;
+ base::ReplaceChars(result, "-", kNonBreakingHyphenUTF8, &result);
+ return result;
+}
+
+} // namespace
bool CefGlobalPreferenceManagerImpl::HasPreference(const CefString& name) {
CEF_REQUIRE_UIT_RETURN(false);
@@ -40,6 +58,77 @@ bool CefGlobalPreferenceManagerImpl::SetPreference(const CefString& name,
value, error);
}
+CefRefPtr
+CefGlobalPreferenceManagerImpl::AddPreferenceObserver(
+ const CefString& name,
+ CefRefPtr observer) {
+ CEF_API_REQUIRE_ADDED(CEF_NEXT);
+ CEF_REQUIRE_UIT_RETURN(nullptr);
+ return CefContext::Get()->GetPrefRegistrar()->AddObserver(name, observer);
+}
+
+// static
+void CefPreferenceManager::GetChromeVariationsAsSwitches(
+ std::vector& switches) {
+ CEF_API_REQUIRE_ADDED(CEF_NEXT);
+
+ // Verify that the context is in a valid state.
+ if (!CONTEXT_STATE_VALID()) {
+ DCHECK(false) << "context not valid";
+ return;
+ }
+
+ switches.clear();
+
+ // Based on ChromeFeatureListCreator::ConvertFlagsToSwitches().
+
+ flags_ui::PrefServiceFlagsStorage flags_storage(
+ g_browser_process->local_state());
+ base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+ about_flags::ConvertFlagsToSwitches(&flags_storage, &command_line,
+ flags_ui::kNoSentinels);
+
+ for (const auto& arg : command_line.argv()) {
+ if (!arg.empty()) {
+ switches.push_back(arg);
+ }
+ }
+}
+
+// static
+void CefPreferenceManager::GetChromeVariationsAsStrings(
+ std::vector& strings) {
+ CEF_API_REQUIRE_ADDED(CEF_NEXT);
+
+ // Verify that the context is in a valid state.
+ if (!CONTEXT_STATE_VALID()) {
+ DCHECK(false) << "context not valid";
+ return;
+ }
+
+ strings.clear();
+
+ // Based on components/webui/version/version_handler_helper.cc
+ // GetVariationsList().
+
+ base::FieldTrial::ActiveGroups active_groups;
+ // Include low anonymity trial groups in the version string, as it is only
+ // displayed locally (and is useful for diagnostics purposes).
+ base::FieldTrialListIncludingLowAnonymity::
+ GetActiveFieldTrialGroupsForTesting(&active_groups);
+
+ for (const auto& group : active_groups) {
+ strings.push_back(GetActiveGroupNameAsString(group));
+ }
+
+ // Synthetic field trials.
+ for (const auto& group :
+ variations::SyntheticTrialsActiveGroupIdProvider::GetInstance()
+ ->GetGroups()) {
+ strings.push_back(GetActiveGroupNameAsString(group.active_group()));
+ }
+}
+
// static
CefRefPtr
CefPreferenceManager::GetGlobalPreferenceManager() {
diff --git a/libcef/browser/global_preference_manager_impl.h b/libcef/browser/global_preference_manager_impl.h
index 47551af81..597149af7 100644
--- a/libcef/browser/global_preference_manager_impl.h
+++ b/libcef/browser/global_preference_manager_impl.h
@@ -27,6 +27,9 @@ class CefGlobalPreferenceManagerImpl : public CefPreferenceManager {
bool SetPreference(const CefString& name,
CefRefPtr value,
CefString& error) override;
+ CefRefPtr AddPreferenceObserver(
+ const CefString& name,
+ CefRefPtr observer) override;
private:
IMPLEMENT_REFCOUNTING(CefGlobalPreferenceManagerImpl);
diff --git a/libcef/browser/prefs/pref_helper.cc b/libcef/browser/prefs/pref_helper.cc
index be1eb8949..c2a2e1e97 100644
--- a/libcef/browser/prefs/pref_helper.cc
+++ b/libcef/browser/prefs/pref_helper.cc
@@ -6,6 +6,7 @@
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
+#include "cef/include/cef_preference.h"
#include "cef/libcef/browser/thread_util.h"
#include "cef/libcef/common/values_impl.h"
#include "components/prefs/pref_service.h"
@@ -118,4 +119,145 @@ bool SetPreference(PrefService* pref_service,
return true;
}
+class RegistrationImpl final : public Registration, public CefRegistration {
+ public:
+ RegistrationImpl(Registrar* registrar,
+ const CefString& name,
+ CefRefPtr observer)
+ : registrar_(registrar), name_(name), observer_(observer) {
+ DCHECK(registrar_);
+ DCHECK(observer_);
+ }
+
+ RegistrationImpl(const RegistrationImpl&) = delete;
+ RegistrationImpl& operator=(const RegistrationImpl&) = delete;
+
+ ~RegistrationImpl() override {
+ CEF_REQUIRE_UIT();
+ if (registrar_) {
+ registrar_->RemoveObserver(name_.ToString(), this);
+ }
+ }
+
+ void Detach() override {
+ registrar_ = nullptr;
+ observer_ = nullptr;
+ }
+
+ void RunCallback() const override { RunCallback(name_); }
+
+ void RunCallback(const CefString& name) const override {
+ observer_->OnPreferenceChanged(name);
+ }
+
+ private:
+ raw_ptr registrar_;
+ CefString name_;
+ CefRefPtr observer_;
+
+ IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(RegistrationImpl);
+};
+
+Registrar::~Registrar() {
+ RemoveAll();
+}
+
+void Registrar::Init(PrefService* service) {
+ DCHECK(service);
+ DCHECK(IsEmpty() || service_ == service);
+ service_ = service;
+}
+
+void Registrar::Reset() {
+ RemoveAll();
+ service_ = nullptr;
+}
+
+void Registrar::RemoveAll() {
+ if (!name_observers_.empty()) {
+ for (auto& [name, registrations] : name_observers_) {
+ service_->RemovePrefObserver(name, this);
+ for (auto& registration : registrations) {
+ registration.Detach();
+ }
+ }
+ name_observers_.clear();
+ }
+
+ if (!all_observers_.empty()) {
+ service_->RemovePrefObserverAllPrefs(this);
+ for (auto& registration : all_observers_) {
+ registration.Detach();
+ }
+ all_observers_.Clear();
+ }
+}
+
+bool Registrar::IsEmpty() const {
+ return name_observers_.empty() && all_observers_.empty();
+}
+
+CefRefPtr Registrar::AddObserver(
+ const CefString& name,
+ CefRefPtr observer) {
+ CHECK(service_);
+
+ RegistrationImpl* impl = new RegistrationImpl(this, name, observer);
+
+ if (name.empty()) {
+ if (all_observers_.empty()) {
+ service_->AddPrefObserverAllPrefs(this);
+ }
+ all_observers_.AddObserver(impl);
+ } else {
+ const std::string& name_str = name.ToString();
+ if (!name_observers_.contains(name_str)) {
+ service_->AddPrefObserver(name_str, this);
+ }
+ name_observers_[name_str].AddObserver(impl);
+ }
+
+ return impl;
+}
+
+void Registrar::RemoveObserver(std::string_view name,
+ Registration* registration) {
+ CHECK(service_);
+
+ if (name.empty()) {
+ all_observers_.RemoveObserver(registration);
+ if (all_observers_.empty()) {
+ service_->RemovePrefObserverAllPrefs(this);
+ }
+ } else {
+ auto it = name_observers_.find(std::string(name));
+ DCHECK(it != name_observers_.end());
+ it->second.RemoveObserver(registration);
+ if (it->second.empty()) {
+ name_observers_.erase(it);
+ service_->RemovePrefObserver(name, this);
+ }
+ }
+}
+
+void Registrar::OnPreferenceChanged(PrefService* service,
+ std::string_view pref_name) {
+ std::string pref_name_str(pref_name);
+ if (!name_observers_.empty()) {
+ auto it = name_observers_.find(pref_name_str);
+ if (it != name_observers_.end()) {
+ for (Registration& registration : it->second) {
+ registration.RunCallback();
+ }
+ }
+ }
+
+ if (!all_observers_.empty()) {
+ CefString name_str(pref_name_str);
+ for (Registration& registration : all_observers_) {
+ registration.RunCallback(name_str);
+ }
+ }
+}
+
} // namespace pref_helper
diff --git a/libcef/browser/prefs/pref_helper.h b/libcef/browser/prefs/pref_helper.h
index d8ac2a5a4..4ec5cd843 100644
--- a/libcef/browser/prefs/pref_helper.h
+++ b/libcef/browser/prefs/pref_helper.h
@@ -5,8 +5,16 @@
#ifndef CEF_LIBCEF_BROWSER_PREFS_PREF_HELPER_H_
#define CEF_LIBCEF_BROWSER_PREFS_PREF_HELPER_H_
-#include "cef/include/cef_values.h"
+#include
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "cef/include/cef_registration.h"
+#include "cef/include/cef_values.h"
+#include "components/prefs/pref_observer.h"
+
+class CefPreferenceObserver;
+class CefRegistration;
class PrefService;
namespace pref_helper {
@@ -28,6 +36,69 @@ bool SetPreference(PrefService* pref_service,
CefRefPtr value,
CefString& error);
+class Registration : public base::CheckedObserver {
+ public:
+ virtual void Detach() = 0;
+ virtual void RunCallback() const = 0;
+ virtual void RunCallback(const CefString& name) const = 0;
+};
+
+class RegistrationImpl;
+
+// Automatically manages the registration of one or more CefPreferenceObserver
+// objects with a PrefService. When the Registrar is destroyed, all registered
+// observers are automatically unregistered with the PrefService. Loosely based
+// on PrefChangeRegistrar.
+class Registrar final : public PrefObserver {
+ public:
+ Registrar() = default;
+
+ Registrar(const Registrar&) = delete;
+ Registrar& operator=(const Registrar&) = delete;
+
+ ~Registrar();
+
+ // Must be called before adding or removing observers. Can be called more
+ // than once as long as the value of |service| doesn't change.
+ void Init(PrefService* service);
+
+ // Removes all observers and clears the reference to the PrefService.
+ // `Init` must be called before adding or removing any observers.
+ void Reset();
+
+ // Removes all observers that have been previously added with a call to Add.
+ void RemoveAll();
+
+ // Returns true if no observers are registered.
+ bool IsEmpty() const;
+
+ // Adds a pref |observer| for the specified pref |name|. All registered
+ // observers will be automatically unregistered and detached when the
+ // Registrar's destructor is called.
+ CefRefPtr AddObserver(
+ const CefString& name,
+ CefRefPtr observer);
+
+ private:
+ friend class RegistrationImpl;
+
+ void RemoveObserver(std::string_view name, Registration* registration);
+
+ // PrefObserver:
+ void OnPreferenceChanged(PrefService* service,
+ std::string_view pref_name) override;
+
+ raw_ptr service_ = nullptr;
+
+ // Observers registered for a preference by name.
+ using ObserverMap =
+ std::unordered_map>;
+ ObserverMap name_observers_;
+
+ // Observers registered for all preferences.
+ base::ObserverList all_observers_;
+};
+
} // namespace pref_helper
#endif // CEF_LIBCEF_BROWSER_PREFS_PREF_HELPER_H_
diff --git a/libcef/browser/request_context_impl.cc b/libcef/browser/request_context_impl.cc
index 0be80fc37..1a8f43b15 100644
--- a/libcef/browser/request_context_impl.cc
+++ b/libcef/browser/request_context_impl.cc
@@ -10,7 +10,9 @@
#include "cef/libcef/browser/browser_context.h"
#include "cef/libcef/browser/context.h"
#include "cef/libcef/browser/prefs/pref_helper.h"
+#include "cef/libcef/browser/setting_helper.h"
#include "cef/libcef/browser/thread_util.h"
+#include "cef/libcef/common/api_version_util.h"
#include "cef/libcef/common/app_manager.h"
#include "cef/libcef/common/task_runner_impl.h"
#include "cef/libcef/common/values_impl.h"
@@ -459,6 +461,22 @@ bool CefRequestContextImpl::SetPreference(const CefString& name,
return pref_helper::SetPreference(pref_service, name, value, error);
}
+CefRefPtr CefRequestContextImpl::AddPreferenceObserver(
+ const CefString& name,
+ CefRefPtr observer) {
+ CEF_API_REQUIRE_ADDED(CEF_NEXT);
+ if (!VerifyBrowserContext()) {
+ return nullptr;
+ }
+
+ if (!pref_registrar_) {
+ pref_registrar_ = std::make_unique();
+ pref_registrar_->Init(browser_context()->AsProfile()->GetPrefs());
+ }
+
+ return pref_registrar_->AddObserver(name, observer);
+}
+
void CefRequestContextImpl::ClearCertificateExceptions(
CefRefPtr callback) {
GetBrowserContext(
@@ -588,6 +606,26 @@ void CefRequestContextImpl::SetContentSetting(
requesting_url, top_level_url, content_type, value));
}
+CefRefPtr CefRequestContextImpl::AddSettingObserver(
+ CefRefPtr observer) {
+ CEF_API_REQUIRE_ADDED(CEF_NEXT);
+ if (!VerifyBrowserContext()) {
+ return nullptr;
+ }
+
+ if (!setting_registrar_) {
+ auto* settings_map = HostContentSettingsMapFactory::GetForProfile(
+ browser_context()->AsProfile());
+ if (!settings_map) {
+ return nullptr;
+ }
+ setting_registrar_ = std::make_unique();
+ setting_registrar_->Init(settings_map);
+ }
+
+ return setting_registrar_->AddObserver(observer);
+}
+
void CefRequestContextImpl::SetChromeColorScheme(cef_color_variant_t variant,
cef_color_t user_color) {
GetBrowserContext(
diff --git a/libcef/browser/request_context_impl.h b/libcef/browser/request_context_impl.h
index c70ebd206..f7f078a96 100644
--- a/libcef/browser/request_context_impl.h
+++ b/libcef/browser/request_context_impl.h
@@ -17,6 +17,14 @@ namespace content {
struct GlobalRenderFrameHostId;
}
+namespace pref_helper {
+class Registrar;
+}
+
+namespace setting_helper {
+class Registrar;
+}
+
class CefBrowserContext;
// Implementation of the CefRequestContext interface. All methods are thread-
@@ -80,6 +88,20 @@ class CefRequestContextImpl : public CefRequestContext {
scoped_refptr task_runner,
BrowserContextCallback callback);
+ // CefPreferenceManager methods.
+ bool HasPreference(const CefString& name) override;
+ CefRefPtr GetPreference(const CefString& name) override;
+ CefRefPtr GetAllPreferences(
+ bool include_defaults) override;
+ bool CanSetPreference(const CefString& name) override;
+ bool SetPreference(const CefString& name,
+ CefRefPtr value,
+ CefString& error) override;
+ CefRefPtr AddPreferenceObserver(
+ const CefString& name,
+ CefRefPtr observer) override;
+
+ // CefRequestContext methods.
bool IsSame(CefRefPtr other) override;
bool IsSharingWith(CefRefPtr other) override;
bool IsGlobal() override;
@@ -92,14 +114,6 @@ class CefRequestContextImpl : public CefRequestContext {
const CefString& domain_name,
CefRefPtr factory) override;
bool ClearSchemeHandlerFactories() override;
- bool HasPreference(const CefString& name) override;
- CefRefPtr GetPreference(const CefString& name) override;
- CefRefPtr GetAllPreferences(
- bool include_defaults) override;
- bool CanSetPreference(const CefString& name) override;
- bool SetPreference(const CefString& name,
- CefRefPtr value,
- CefString& error) override;
void ClearCertificateExceptions(
CefRefPtr callback) override;
void ClearHttpAuthCredentials(
@@ -125,6 +139,8 @@ class CefRequestContextImpl : public CefRequestContext {
const CefString& top_level_url,
cef_content_setting_types_t content_type,
cef_content_setting_values_t value) override;
+ CefRefPtr AddSettingObserver(
+ CefRefPtr observer) override;
void SetChromeColorScheme(cef_color_variant_t variant,
cef_color_t user_color) override;
cef_color_variant_t GetChromeColorSchemeMode() override;
@@ -218,6 +234,9 @@ class CefRequestContextImpl : public CefRequestContext {
Config config_;
+ std::unique_ptr pref_registrar_;
+ std::unique_ptr setting_registrar_;
+
IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefRequestContextImpl);
};
diff --git a/libcef/browser/setting_helper.cc b/libcef/browser/setting_helper.cc
new file mode 100644
index 000000000..78a07f34b
--- /dev/null
+++ b/libcef/browser/setting_helper.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2025 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that can
+// be found in the LICENSE file.
+
+#include "cef/libcef/browser/setting_helper.h"
+
+#include "cef/include/cef_request_context.h"
+#include "cef/libcef/browser/thread_util.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "url/gurl.h"
+
+namespace setting_helper {
+
+class RegistrationImpl final : public Registration, public CefRegistration {
+ public:
+ RegistrationImpl(Registrar* registrar, CefRefPtr observer)
+ : registrar_(registrar), observer_(observer) {
+ DCHECK(registrar_);
+ DCHECK(observer_);
+ }
+
+ RegistrationImpl(const RegistrationImpl&) = delete;
+ RegistrationImpl& operator=(const RegistrationImpl&) = delete;
+
+ ~RegistrationImpl() override {
+ CEF_REQUIRE_UIT();
+ if (registrar_) {
+ registrar_->RemoveObserver(this);
+ }
+ }
+
+ void Detach() override {
+ registrar_ = nullptr;
+ observer_ = nullptr;
+ }
+
+ void RunCallback(const CefString& requesting_url,
+ const CefString& top_level_url,
+ cef_content_setting_types_t content_type) const override {
+ observer_->OnSettingChanged(requesting_url, top_level_url, content_type);
+ }
+
+ private:
+ raw_ptr registrar_;
+ CefRefPtr observer_;
+
+ IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(RegistrationImpl);
+};
+
+Registrar::~Registrar() {
+ RemoveAll();
+}
+
+void Registrar::Init(HostContentSettingsMap* settings) {
+ DCHECK(settings);
+ DCHECK(IsEmpty() || settings_ == settings);
+ settings_ = settings;
+}
+
+void Registrar::Reset() {
+ RemoveAll();
+ settings_ = nullptr;
+}
+
+void Registrar::RemoveAll() {
+ if (!observers_.empty()) {
+ settings_->RemoveObserver(this);
+ for (auto& registration : observers_) {
+ registration.Detach();
+ }
+ observers_.Clear();
+ }
+}
+
+bool Registrar::IsEmpty() const {
+ return observers_.empty();
+}
+
+CefRefPtr Registrar::AddObserver(
+ CefRefPtr observer) {
+ CHECK(settings_);
+
+ RegistrationImpl* impl = new RegistrationImpl(this, observer);
+
+ if (observers_.empty()) {
+ settings_->AddObserver(this);
+ }
+ observers_.AddObserver(impl);
+
+ return impl;
+}
+
+void Registrar::RemoveObserver(Registration* registration) {
+ CHECK(settings_);
+
+ observers_.RemoveObserver(registration);
+ if (observers_.empty()) {
+ settings_->RemoveObserver(this);
+ }
+}
+
+void Registrar::OnContentSettingChanged(
+ const ContentSettingsPattern& primary_pattern,
+ const ContentSettingsPattern& secondary_pattern,
+ ContentSettingsTypeSet content_type_set) {
+ DCHECK(!IsEmpty());
+
+ const CefString requesting_url(primary_pattern.ToRepresentativeUrl().spec());
+ const CefString top_level_url(secondary_pattern.ToRepresentativeUrl().spec());
+ const auto content_type =
+ static_cast(content_type_set.GetType());
+
+ for (Registration& registration : observers_) {
+ registration.RunCallback(requesting_url, top_level_url, content_type);
+ }
+}
+
+} // namespace setting_helper
diff --git a/libcef/browser/setting_helper.h b/libcef/browser/setting_helper.h
new file mode 100644
index 000000000..e8f702910
--- /dev/null
+++ b/libcef/browser/setting_helper.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2025 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that can
+// be found in the LICENSE file.
+
+#ifndef CEF_LIBCEF_BROWSER_SETTING_HELPER_H_
+#define CEF_LIBCEF_BROWSER_SETTING_HELPER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "cef/include/cef_registration.h"
+#include "components/content_settings/core/browser/content_settings_observer.h"
+
+class CefSettingObserver;
+class CefRegistration;
+class HostContentSettingsMap;
+
+namespace setting_helper {
+
+class Registration : public base::CheckedObserver {
+ public:
+ virtual void Detach() = 0;
+ virtual void RunCallback(const CefString& requesting_url,
+ const CefString& top_level_url,
+ cef_content_setting_types_t content_type) const = 0;
+};
+
+class RegistrationImpl;
+
+// Automatically manages the registration of one or more CefSettingObserver
+// objects with a HostContentSettingsMap. When the Registrar is destroyed, all
+// registered observers are automatically unregistered with the
+// HostContentSettingsMap. Loosely based on PrefChangeRegistrar.
+class Registrar final : public content_settings::Observer {
+ public:
+ Registrar() = default;
+
+ Registrar(const Registrar&) = delete;
+ Registrar& operator=(const Registrar&) = delete;
+
+ ~Registrar();
+
+ // Must be called before adding or removing observers. Can be called more
+ // than once as long as the value of |settings| doesn't change.
+ void Init(HostContentSettingsMap* settings);
+
+ // Removes all observers and clears the reference to the
+ // HostContentSettingsMap. `Init` must be called before adding or removing any
+ // observers.
+ void Reset();
+
+ // Removes all observers that have been previously added with a call to Add.
+ void RemoveAll();
+
+ // Returns true if no observers are registered.
+ bool IsEmpty() const;
+
+ // Adds a setting observer. All registered observers will be automatically
+ // unregistered and detached when the Registrar's destructor is called.
+ CefRefPtr AddObserver(
+ CefRefPtr observer);
+
+ private:
+ friend class RegistrationImpl;
+
+ void RemoveObserver(Registration* registration);
+
+ // content_settings::Observer:
+ void OnContentSettingChanged(
+ const ContentSettingsPattern& primary_pattern,
+ const ContentSettingsPattern& secondary_pattern,
+ ContentSettingsTypeSet content_type_set) override;
+
+ raw_ptr settings_ =
+ nullptr;
+
+ base::ObserverList observers_;
+};
+
+} // namespace setting_helper
+
+#endif // CEF_LIBCEF_BROWSER_SETTING_HELPER_H_
diff --git a/patch/patch.cfg b/patch/patch.cfg
index 3b864ac06..66c3940f7 100644
--- a/patch/patch.cfg
+++ b/patch/patch.cfg
@@ -764,5 +764,14 @@ patches = [
# Expose Mojo Connector error state to Receiver disconnect handlers.
# https://github.com/chromiumembedded/cef/issues/3664
'name': 'mojo_connect_result_3664'
+ },
+ {
+ # Support for configuration accessors/observers.
+ # - Allow pref_service::Registrar access to
+ # PrefService::[Add|Remove]PrefObserver.
+ # - Enable SyntheticTrialsActiveGroupIdProvider::GetGroups in Release
+ # builds.
+ # https://github.com/chromiumembedded/cef/issues/3892
+ 'name': 'config_3892'
}
]
diff --git a/patch/patches/config_3892.patch b/patch/patches/config_3892.patch
new file mode 100644
index 000000000..939fe0e13
--- /dev/null
+++ b/patch/patches/config_3892.patch
@@ -0,0 +1,85 @@
+diff --git components/prefs/pref_service.h components/prefs/pref_service.h
+index f1856d6ee4419..413b77e80d84d 100644
+--- components/prefs/pref_service.h
++++ components/prefs/pref_service.h
+@@ -52,6 +52,10 @@ namespace base {
+ class FilePath;
+ }
+
++namespace pref_helper {
++class Registrar;
++}
++
+ namespace prefs {
+ class ScopedDictionaryPrefUpdate;
+ }
+@@ -441,6 +445,8 @@ class COMPONENTS_PREFS_EXPORT PrefService {
+ // declared as a friend, too.
+ friend class PrefChangeRegistrar;
+ friend class subtle::PrefMemberBase;
++ // CEF registration manager.
++ friend class pref_helper::Registrar;
+
+ // These are protected so they can only be accessed by the friend
+ // classes listed above.
+diff --git components/variations/synthetic_trials_active_group_id_provider.cc components/variations/synthetic_trials_active_group_id_provider.cc
+index bd51697297471..3e669cd080457 100644
+--- components/variations/synthetic_trials_active_group_id_provider.cc
++++ components/variations/synthetic_trials_active_group_id_provider.cc
+@@ -27,7 +27,7 @@ SyntheticTrialsActiveGroupIdProvider::GetActiveGroupIds() {
+ return group_ids_;
+ }
+
+-#if !defined(NDEBUG)
++#if !defined(NDEBUG) || BUILDFLAG(ENABLE_CEF)
+ std::vector
+ SyntheticTrialsActiveGroupIdProvider::GetGroups() {
+ base::AutoLock scoped_lock(lock_);
+@@ -38,7 +38,7 @@ SyntheticTrialsActiveGroupIdProvider::GetGroups() {
+ void SyntheticTrialsActiveGroupIdProvider::ResetForTesting() {
+ base::AutoLock scoped_lock(lock_);
+ group_ids_.clear();
+-#if !defined(NDEBUG)
++#if !defined(NDEBUG) || BUILDFLAG(ENABLE_CEF)
+ groups_.clear();
+ #endif // !defined(NDEBUG)
+ }
+@@ -53,7 +53,7 @@ void SyntheticTrialsActiveGroupIdProvider::OnSyntheticTrialsChanged(
+ for (const auto& group : groups) {
+ group_ids_.push_back(group.id());
+ }
+-#if !defined(NDEBUG)
++#if !defined(NDEBUG) || BUILDFLAG(ENABLE_CEF)
+ groups_ = groups;
+ #endif // !defined(NDEBUG)
+ }
+diff --git components/variations/synthetic_trials_active_group_id_provider.h components/variations/synthetic_trials_active_group_id_provider.h
+index b4a999731d996..0bbac173fddf1 100644
+--- components/variations/synthetic_trials_active_group_id_provider.h
++++ components/variations/synthetic_trials_active_group_id_provider.h
+@@ -10,6 +10,7 @@
+ #include "base/component_export.h"
+ #include "base/synchronization/lock.h"
+ #include "base/thread_annotations.h"
++#include "cef/libcef/features/features.h"
+ #include "components/variations/active_field_trials.h"
+ #include "components/variations/synthetic_trials.h"
+
+@@ -36,7 +37,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialsActiveGroupIdProvider
+ // Returns currently active synthetic trial group IDs.
+ std::vector GetActiveGroupIds();
+
+-#if !defined(NDEBUG)
++#if !defined(NDEBUG) || BUILDFLAG(ENABLE_CEF)
+ // In debug mode, not only the group IDs are tracked but also the full group
+ // info, to display the names unhashed in chrome://version.
+ std::vector GetGroups();
+@@ -60,7 +61,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialsActiveGroupIdProvider
+
+ base::Lock lock_;
+ std::vector group_ids_; // GUARDED_BY(lock_);
+-#if !defined(NDEBUG)
++#if !defined(NDEBUG) || BUILDFLAG(ENABLE_CEF)
+ // In debug builds, keep the full group information to be able to display it
+ // in chrome://version.
+ std::vector groups_; // GUARDED_BY(lock_);
diff --git a/tests/cefclient/browser/config_test.cc b/tests/cefclient/browser/config_test.cc
new file mode 100644
index 000000000..58001c889
--- /dev/null
+++ b/tests/cefclient/browser/config_test.cc
@@ -0,0 +1,334 @@
+// Copyright (c) 2025 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that
+// can be found in the LICENSE file.
+
+#include "tests/cefclient/browser/config_test.h"
+
+#include
+
+
+
+
+
diff --git a/tests/cefclient/resources/other_tests.html b/tests/cefclient/resources/other_tests.html
index 9e8fcbaec..2b311ba02 100644
--- a/tests/cefclient/resources/other_tests.html
+++ b/tests/cefclient/resources/other_tests.html
@@ -10,6 +10,7 @@
Authentication (Basic) - credentials returned via GetAuthCredentials
Authentication (Digest) - credentials returned via GetAuthCredentials
Binary vs String Transfer Benchmark
+Chrome Configuration
Cursors
Dialogs
Drag & Drop
diff --git a/tests/cefclient/resources/preferences.html b/tests/cefclient/resources/preferences.html
index 8ebe74690..b960cf9ef 100644
--- a/tests/cefclient/resources/preferences.html
+++ b/tests/cefclient/resources/preferences.html
@@ -13,8 +13,12 @@