mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
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.
This commit is contained in:
2
BUILD.gn
2
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",
|
||||
|
@ -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',
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "include/cef_base.h"
|
||||
#include "include/cef_registration.h"
|
||||
#include "include/cef_values.h"
|
||||
|
||||
///
|
||||
@ -65,6 +66,24 @@ class CefPreferenceRegistrar : public CefBaseScoped {
|
||||
CefRefPtr<CefValue> 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<CefString>& 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<CefString>& strings);
|
||||
#endif
|
||||
|
||||
///
|
||||
/// Returns the global preference manager object.
|
||||
///
|
||||
@ -129,6 +178,21 @@ class CefPreferenceManager : public virtual CefBaseRefCounted {
|
||||
virtual bool SetPreference(const CefString& name,
|
||||
CefRefPtr<CefValue> 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<CefRegistration> AddPreferenceObserver(
|
||||
const CefString& name,
|
||||
CefRefPtr<CefPreferenceObserver> observer) = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // CEF_INCLUDE_CEF_PREFERENCE_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<CefString>& 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<CefRegistration> AddSettingObserver(
|
||||
CefRefPtr<CefSettingObserver> 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
|
||||
|
@ -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<CefTraceSubscriber>();
|
||||
}
|
||||
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_helper::Registrar>();
|
||||
pref_registrar_->Init(g_browser_process->local_state());
|
||||
}
|
||||
return pref_registrar_.get();
|
||||
}
|
||||
|
||||
void CefContext::PopulateGlobalRequestContextSettings(
|
||||
CefRequestContextSettings* settings) {
|
||||
CefRefPtr<CefCommandLine> 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;
|
||||
}
|
||||
|
@ -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<CefMainRunner> main_runner_;
|
||||
std::unique_ptr<CefTraceSubscriber> trace_subscriber_;
|
||||
std::unique_ptr<pref_helper::Registrar> pref_registrar_;
|
||||
std::unique_ptr<CefBrowserInfoManager> browser_info_manager_;
|
||||
|
||||
// Observers that want to be notified of changes to this object.
|
||||
|
@ -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<CefRegistration>
|
||||
CefGlobalPreferenceManagerImpl::AddPreferenceObserver(
|
||||
const CefString& name,
|
||||
CefRefPtr<CefPreferenceObserver> 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<CefString>& 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<CefString>& 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>
|
||||
CefPreferenceManager::GetGlobalPreferenceManager() {
|
||||
|
@ -27,6 +27,9 @@ class CefGlobalPreferenceManagerImpl : public CefPreferenceManager {
|
||||
bool SetPreference(const CefString& name,
|
||||
CefRefPtr<CefValue> value,
|
||||
CefString& error) override;
|
||||
CefRefPtr<CefRegistration> AddPreferenceObserver(
|
||||
const CefString& name,
|
||||
CefRefPtr<CefPreferenceObserver> observer) override;
|
||||
|
||||
private:
|
||||
IMPLEMENT_REFCOUNTING(CefGlobalPreferenceManagerImpl);
|
||||
|
@ -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<CefPreferenceObserver> 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> registrar_;
|
||||
CefString name_;
|
||||
CefRefPtr<CefPreferenceObserver> 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<CefRegistration> Registrar::AddObserver(
|
||||
const CefString& name,
|
||||
CefRefPtr<CefPreferenceObserver> 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
|
||||
|
@ -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 <unordered_map>
|
||||
|
||||
#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<CefValue> 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<CefRegistration> AddObserver(
|
||||
const CefString& name,
|
||||
CefRefPtr<CefPreferenceObserver> 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<PrefService, AcrossTasksDanglingUntriaged> service_ = nullptr;
|
||||
|
||||
// Observers registered for a preference by name.
|
||||
using ObserverMap =
|
||||
std::unordered_map<std::string, base::ObserverList<Registration>>;
|
||||
ObserverMap name_observers_;
|
||||
|
||||
// Observers registered for all preferences.
|
||||
base::ObserverList<Registration> all_observers_;
|
||||
};
|
||||
|
||||
} // namespace pref_helper
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_PREFS_PREF_HELPER_H_
|
||||
|
@ -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<CefRegistration> CefRequestContextImpl::AddPreferenceObserver(
|
||||
const CefString& name,
|
||||
CefRefPtr<CefPreferenceObserver> observer) {
|
||||
CEF_API_REQUIRE_ADDED(CEF_NEXT);
|
||||
if (!VerifyBrowserContext()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!pref_registrar_) {
|
||||
pref_registrar_ = std::make_unique<pref_helper::Registrar>();
|
||||
pref_registrar_->Init(browser_context()->AsProfile()->GetPrefs());
|
||||
}
|
||||
|
||||
return pref_registrar_->AddObserver(name, observer);
|
||||
}
|
||||
|
||||
void CefRequestContextImpl::ClearCertificateExceptions(
|
||||
CefRefPtr<CefCompletionCallback> callback) {
|
||||
GetBrowserContext(
|
||||
@ -588,6 +606,26 @@ void CefRequestContextImpl::SetContentSetting(
|
||||
requesting_url, top_level_url, content_type, value));
|
||||
}
|
||||
|
||||
CefRefPtr<CefRegistration> CefRequestContextImpl::AddSettingObserver(
|
||||
CefRefPtr<CefSettingObserver> 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_helper::Registrar>();
|
||||
setting_registrar_->Init(settings_map);
|
||||
}
|
||||
|
||||
return setting_registrar_->AddObserver(observer);
|
||||
}
|
||||
|
||||
void CefRequestContextImpl::SetChromeColorScheme(cef_color_variant_t variant,
|
||||
cef_color_t user_color) {
|
||||
GetBrowserContext(
|
||||
|
@ -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<base::SingleThreadTaskRunner> task_runner,
|
||||
BrowserContextCallback callback);
|
||||
|
||||
// CefPreferenceManager methods.
|
||||
bool HasPreference(const CefString& name) override;
|
||||
CefRefPtr<CefValue> GetPreference(const CefString& name) override;
|
||||
CefRefPtr<CefDictionaryValue> GetAllPreferences(
|
||||
bool include_defaults) override;
|
||||
bool CanSetPreference(const CefString& name) override;
|
||||
bool SetPreference(const CefString& name,
|
||||
CefRefPtr<CefValue> value,
|
||||
CefString& error) override;
|
||||
CefRefPtr<CefRegistration> AddPreferenceObserver(
|
||||
const CefString& name,
|
||||
CefRefPtr<CefPreferenceObserver> observer) override;
|
||||
|
||||
// CefRequestContext methods.
|
||||
bool IsSame(CefRefPtr<CefRequestContext> other) override;
|
||||
bool IsSharingWith(CefRefPtr<CefRequestContext> other) override;
|
||||
bool IsGlobal() override;
|
||||
@ -92,14 +114,6 @@ class CefRequestContextImpl : public CefRequestContext {
|
||||
const CefString& domain_name,
|
||||
CefRefPtr<CefSchemeHandlerFactory> factory) override;
|
||||
bool ClearSchemeHandlerFactories() override;
|
||||
bool HasPreference(const CefString& name) override;
|
||||
CefRefPtr<CefValue> GetPreference(const CefString& name) override;
|
||||
CefRefPtr<CefDictionaryValue> GetAllPreferences(
|
||||
bool include_defaults) override;
|
||||
bool CanSetPreference(const CefString& name) override;
|
||||
bool SetPreference(const CefString& name,
|
||||
CefRefPtr<CefValue> value,
|
||||
CefString& error) override;
|
||||
void ClearCertificateExceptions(
|
||||
CefRefPtr<CefCompletionCallback> 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<CefRegistration> AddSettingObserver(
|
||||
CefRefPtr<CefSettingObserver> 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_helper::Registrar> pref_registrar_;
|
||||
std::unique_ptr<setting_helper::Registrar> setting_registrar_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefRequestContextImpl);
|
||||
};
|
||||
|
||||
|
118
libcef/browser/setting_helper.cc
Normal file
118
libcef/browser/setting_helper.cc
Normal file
@ -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<CefSettingObserver> 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> registrar_;
|
||||
CefRefPtr<CefSettingObserver> 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<CefRegistration> Registrar::AddObserver(
|
||||
CefRefPtr<CefSettingObserver> 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<cef_content_setting_types_t>(content_type_set.GetType());
|
||||
|
||||
for (Registration& registration : observers_) {
|
||||
registration.RunCallback(requesting_url, top_level_url, content_type);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace setting_helper
|
81
libcef/browser/setting_helper.h
Normal file
81
libcef/browser/setting_helper.h
Normal file
@ -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<CefRegistration> AddObserver(
|
||||
CefRefPtr<CefSettingObserver> 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<HostContentSettingsMap, AcrossTasksDanglingUntriaged> settings_ =
|
||||
nullptr;
|
||||
|
||||
base::ObserverList<Registration> observers_;
|
||||
};
|
||||
|
||||
} // namespace setting_helper
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_SETTING_HELPER_H_
|
@ -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'
|
||||
}
|
||||
]
|
||||
|
85
patch/patches/config_3892.patch
Normal file
85
patch/patches/config_3892.patch
Normal file
@ -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<SyntheticTrialGroup>
|
||||
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<ActiveGroupId> 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<SyntheticTrialGroup> GetGroups();
|
||||
@@ -60,7 +61,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialsActiveGroupIdProvider
|
||||
|
||||
base::Lock lock_;
|
||||
std::vector<ActiveGroupId> 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<SyntheticTrialGroup> groups_; // GUARDED_BY(lock_);
|
334
tests/cefclient/browser/config_test.cc
Normal file
334
tests/cefclient/browser/config_test.cc
Normal file
@ -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 <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "include/base/cef_logging.h"
|
||||
#include "include/cef_parser.h"
|
||||
#include "include/cef_request_context.h"
|
||||
#include "tests/cefclient/browser/test_runner.h"
|
||||
|
||||
namespace client::config_test {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kTestUrlPath[] = "/config";
|
||||
|
||||
// Application-specific error codes.
|
||||
const int kMessageFormatError = 1;
|
||||
const int kRequestFailedError = 2;
|
||||
|
||||
// Common to all messages.
|
||||
const char kNameKey[] = "name";
|
||||
const char kNameGlobalConfig[] = "global_config";
|
||||
const char kNameSubscribe[] = "subscribe";
|
||||
|
||||
// Convert a dictionary value to a JSON string.
|
||||
CefString GetJSON(CefRefPtr<CefDictionaryValue> dictionary) {
|
||||
CefRefPtr<CefValue> value = CefValue::Create();
|
||||
value->SetDictionary(dictionary);
|
||||
return CefWriteJSON(value, JSON_WRITER_DEFAULT);
|
||||
}
|
||||
|
||||
using CallbackType = CefMessageRouterBrowserSide::Callback;
|
||||
|
||||
void SendSuccess(CefRefPtr<CallbackType> callback,
|
||||
CefRefPtr<CefDictionaryValue> result) {
|
||||
callback->Success(GetJSON(result));
|
||||
}
|
||||
|
||||
void SendFailure(CefRefPtr<CallbackType> callback,
|
||||
int error_code,
|
||||
const std::string& error_message) {
|
||||
callback->Failure(error_code, error_message);
|
||||
}
|
||||
|
||||
class PreferenceObserver : public CefPreferenceObserver {
|
||||
public:
|
||||
PreferenceObserver(CefRefPtr<CefPreferenceManager> manager,
|
||||
bool global,
|
||||
CefRefPtr<CallbackType> callback)
|
||||
: manager_(manager), global_(global), callback_(callback) {}
|
||||
|
||||
PreferenceObserver(const PreferenceObserver&) = delete;
|
||||
PreferenceObserver& operator=(const PreferenceObserver&) = delete;
|
||||
|
||||
void OnPreferenceChanged(const CefString& name) override {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
auto value = manager_->GetPreference(name);
|
||||
|
||||
auto payload = CefDictionaryValue::Create();
|
||||
payload->SetString("type", "preference");
|
||||
payload->SetBool("global", global_);
|
||||
payload->SetString("name", name);
|
||||
if (value) {
|
||||
payload->SetInt("value_type", value->GetType());
|
||||
payload->SetValue("value", value);
|
||||
} else {
|
||||
payload->SetInt("value_type", VTYPE_NULL);
|
||||
payload->SetNull("value");
|
||||
}
|
||||
|
||||
SendSuccess(callback_, payload);
|
||||
}
|
||||
|
||||
private:
|
||||
const CefRefPtr<CefPreferenceManager> manager_;
|
||||
const bool global_;
|
||||
const CefRefPtr<CallbackType> callback_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(PreferenceObserver);
|
||||
};
|
||||
|
||||
class SettingObserver : public CefSettingObserver {
|
||||
public:
|
||||
SettingObserver(CefRefPtr<CefRequestContext> context,
|
||||
CefRefPtr<CallbackType> callback)
|
||||
: context_(context), callback_(callback) {}
|
||||
|
||||
SettingObserver(const SettingObserver&) = delete;
|
||||
SettingObserver& operator=(const SettingObserver&) = delete;
|
||||
|
||||
void OnSettingChanged(const CefString& requesting_url,
|
||||
const CefString& top_level_url,
|
||||
cef_content_setting_types_t content_type) override {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
auto value = context_->GetWebsiteSetting(requesting_url, top_level_url,
|
||||
content_type);
|
||||
|
||||
auto payload = CefDictionaryValue::Create();
|
||||
payload->SetString("type", "setting");
|
||||
payload->SetString("requesting_url", requesting_url);
|
||||
payload->SetString("top_level_url", top_level_url);
|
||||
payload->SetInt("content_type", static_cast<int>(content_type));
|
||||
if (value) {
|
||||
payload->SetInt("value_type", value->GetType());
|
||||
payload->SetValue("value", value);
|
||||
} else {
|
||||
payload->SetInt("value_type", VTYPE_NULL);
|
||||
payload->SetNull("value");
|
||||
}
|
||||
|
||||
SendSuccess(callback_, payload);
|
||||
}
|
||||
|
||||
private:
|
||||
const CefRefPtr<CefRequestContext> context_;
|
||||
const CefRefPtr<CallbackType> callback_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(SettingObserver);
|
||||
};
|
||||
|
||||
// Handle messages in the browser process. Only accessed on the UI thread.
|
||||
class Handler : public CefMessageRouterBrowserSide::Handler {
|
||||
public:
|
||||
typedef std::vector<std::string> NameVector;
|
||||
|
||||
Handler() { CEF_REQUIRE_UI_THREAD(); }
|
||||
|
||||
Handler(const Handler&) = delete;
|
||||
Handler& operator=(const Handler&) = delete;
|
||||
|
||||
~Handler() override {
|
||||
for (auto& pair : subscription_state_map_) {
|
||||
delete pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Called due to cefQuery execution in config.html.
|
||||
bool OnQuery(CefRefPtr<CefBrowser> browser,
|
||||
CefRefPtr<CefFrame> frame,
|
||||
int64_t query_id,
|
||||
const CefString& request,
|
||||
bool persistent,
|
||||
CefRefPtr<Callback> callback) override {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
|
||||
// Only handle messages from the test URL.
|
||||
const std::string& url = frame->GetURL();
|
||||
if (!test_runner::IsTestURL(url, kTestUrlPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse |request| as a JSON dictionary.
|
||||
CefRefPtr<CefDictionaryValue> request_dict = ParseJSON(request);
|
||||
if (!request_dict) {
|
||||
callback->Failure(kMessageFormatError, "Incorrect message format");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify the "name" key.
|
||||
if (!VerifyKey(request_dict, kNameKey, VTYPE_STRING, callback)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string& message_name = request_dict->GetString(kNameKey);
|
||||
if (message_name == kNameGlobalConfig) {
|
||||
// JavaScript is requesting a JSON representation of the global config.
|
||||
|
||||
SendGlobalConfig(callback);
|
||||
return true;
|
||||
} else if (message_name == kNameSubscribe) {
|
||||
// Subscribe to notifications from observers.
|
||||
|
||||
if (!persistent) {
|
||||
SendFailure(callback, kMessageFormatError,
|
||||
"Subscriptions must be persistent");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CreateSubscription(browser, query_id, callback)) {
|
||||
SendFailure(callback, kRequestFailedError,
|
||||
"Browser is already subscribed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
|
||||
CefRefPtr<CefFrame> frame,
|
||||
int64_t query_id) override {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
RemoveSubscription(browser->GetIdentifier(), query_id);
|
||||
}
|
||||
|
||||
private:
|
||||
// Convert a JSON string to a dictionary value.
|
||||
static CefRefPtr<CefDictionaryValue> ParseJSON(const CefString& string) {
|
||||
CefRefPtr<CefValue> value = CefParseJSON(string, JSON_PARSER_RFC);
|
||||
if (value.get() && value->GetType() == VTYPE_DICTIONARY) {
|
||||
return value->GetDictionary();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Verify that |key| exists in |dictionary| and has type |value_type|. Fails
|
||||
// |callback| and returns false on failure.
|
||||
static bool VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,
|
||||
const char* key,
|
||||
cef_value_type_t value_type,
|
||||
CefRefPtr<Callback> callback) {
|
||||
if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) {
|
||||
callback->Failure(
|
||||
kMessageFormatError,
|
||||
"Missing or incorrectly formatted message key: " + std::string(key));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static CefRefPtr<CefListValue> MakeListValue(std::vector<CefString> vec) {
|
||||
if (vec.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto list = CefListValue::Create();
|
||||
list->SetSize(vec.size());
|
||||
size_t idx = 0;
|
||||
for (const auto& val : vec) {
|
||||
list->SetString(idx++, val);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void SendGlobalConfig(CefRefPtr<Callback> callback) {
|
||||
std::vector<CefString> switches;
|
||||
CefPreferenceManager::GetChromeVariationsAsSwitches(switches);
|
||||
std::vector<CefString> strings;
|
||||
CefPreferenceManager::GetChromeVariationsAsStrings(strings);
|
||||
|
||||
auto payload = CefDictionaryValue::Create();
|
||||
|
||||
if (auto list = MakeListValue(switches)) {
|
||||
payload->SetList("switches", list);
|
||||
} else {
|
||||
payload->SetNull("switches");
|
||||
}
|
||||
|
||||
if (auto list = MakeListValue(strings)) {
|
||||
payload->SetList("strings", list);
|
||||
} else {
|
||||
payload->SetNull("strings");
|
||||
}
|
||||
|
||||
SendSuccess(callback, payload);
|
||||
}
|
||||
|
||||
// Subscription state associated with a single browser.
|
||||
struct SubscriptionState {
|
||||
int64_t query_id;
|
||||
CefRefPtr<PreferenceObserver> global_pref_observer;
|
||||
CefRefPtr<CefRegistration> global_pref_registration;
|
||||
CefRefPtr<PreferenceObserver> context_pref_observer;
|
||||
CefRefPtr<CefRegistration> context_pref_registration;
|
||||
CefRefPtr<SettingObserver> context_setting_observer;
|
||||
CefRefPtr<CefRegistration> context_setting_registration;
|
||||
};
|
||||
|
||||
bool CreateSubscription(CefRefPtr<CefBrowser> browser,
|
||||
int64_t query_id,
|
||||
CefRefPtr<Callback> callback) {
|
||||
const int browser_id = browser->GetIdentifier();
|
||||
if (subscription_state_map_.find(browser_id) !=
|
||||
subscription_state_map_.end()) {
|
||||
// An subscription already exists for this browser.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto global_pref_manager =
|
||||
CefPreferenceManager::GetGlobalPreferenceManager();
|
||||
auto request_context = browser->GetHost()->GetRequestContext();
|
||||
|
||||
SubscriptionState* state = new SubscriptionState();
|
||||
state->query_id = query_id;
|
||||
|
||||
state->global_pref_observer =
|
||||
new PreferenceObserver(global_pref_manager, /*global=*/true, callback);
|
||||
state->global_pref_registration =
|
||||
global_pref_manager->AddPreferenceObserver(CefString(),
|
||||
state->global_pref_observer);
|
||||
|
||||
state->context_pref_observer =
|
||||
new PreferenceObserver(request_context, /*global=*/false, callback);
|
||||
state->context_pref_registration = request_context->AddPreferenceObserver(
|
||||
CefString(), state->context_pref_observer);
|
||||
|
||||
state->context_setting_observer =
|
||||
new SettingObserver(request_context, callback);
|
||||
state->context_setting_registration =
|
||||
request_context->AddSettingObserver(state->context_setting_observer);
|
||||
|
||||
subscription_state_map_[browser_id] = state;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveSubscription(int browser_id, int64_t query_id) {
|
||||
SubscriptionStateMap::iterator it =
|
||||
subscription_state_map_.find(browser_id);
|
||||
if (it != subscription_state_map_.end() &&
|
||||
it->second->query_id == query_id) {
|
||||
delete it->second;
|
||||
subscription_state_map_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// Map of browser ID to SubscriptionState object.
|
||||
typedef std::map<int, SubscriptionState*> SubscriptionStateMap;
|
||||
SubscriptionStateMap subscription_state_map_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
|
||||
handlers.insert(new Handler());
|
||||
}
|
||||
|
||||
} // namespace client::config_test
|
18
tests/cefclient/browser/config_test.h
Normal file
18
tests/cefclient/browser/config_test.h
Normal file
@ -0,0 +1,18 @@
|
||||
// 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_TESTS_CEFCLIENT_BROWSER_CONFIG_TEST_H_
|
||||
#define CEF_TESTS_CEFCLIENT_BROWSER_CONFIG_TEST_H_
|
||||
#pragma once
|
||||
|
||||
#include "tests/cefclient/browser/test_runner.h"
|
||||
|
||||
namespace client::config_test {
|
||||
|
||||
// Create message handlers. Called from test_runner.cc.
|
||||
void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers);
|
||||
|
||||
} // namespace client::config_test
|
||||
|
||||
#endif // CEF_TESTS_CEFCLIENT_BROWSER_CONFIG_TEST_H_
|
@ -46,32 +46,33 @@
|
||||
#define IDC_STATIC -1
|
||||
#define IDS_BINARY_TRANSFER_HTML 1000
|
||||
#define IDS_BINDING_HTML 1001
|
||||
#define IDS_DIALOGS_HTML 1002
|
||||
#define IDS_DRAGGABLE_HTML 1003
|
||||
#define IDS_HANG_HTML 1004
|
||||
#define IDS_IPC_PERFORMANCE_HTML 1005
|
||||
#define IDS_LOCALSTORAGE_HTML 1006
|
||||
#define IDS_LOGO_PNG 1007
|
||||
#define IDS_MEDIA_ROUTER_HTML 1008
|
||||
#define IDS_MENU_ICON_1X_PNG 1009
|
||||
#define IDS_MENU_ICON_2X_PNG 1010
|
||||
#define IDS_OSRTEST_HTML 1011
|
||||
#define IDS_OTHER_TESTS_HTML 1012
|
||||
#define IDS_PDF_HTML 1013
|
||||
#define IDS_PDF_PDF 1014
|
||||
#define IDS_PERFORMANCE_HTML 1015
|
||||
#define IDS_PERFORMANCE2_HTML 1016
|
||||
#define IDS_PREFERENCES_HTML 1017
|
||||
#define IDS_RESPONSE_FILTER_HTML 1018
|
||||
#define IDS_SERVER_HTML 1019
|
||||
#define IDS_TASK_MANAGER_HTML 1020
|
||||
#define IDS_TRANSPARENCY_HTML 1021
|
||||
#define IDS_URLREQUEST_HTML 1022
|
||||
#define IDS_WEBSOCKET_HTML 1023
|
||||
#define IDS_WINDOW_HTML 1024
|
||||
#define IDS_WINDOW_ICON_1X_PNG 1025
|
||||
#define IDS_WINDOW_ICON_2X_PNG 1026
|
||||
#define IDS_XMLHTTPREQUEST_HTML 1027
|
||||
#define IDS_CONFIG_HTML 1002
|
||||
#define IDS_DIALOGS_HTML 1003
|
||||
#define IDS_DRAGGABLE_HTML 1004
|
||||
#define IDS_HANG_HTML 1005
|
||||
#define IDS_IPC_PERFORMANCE_HTML 1006
|
||||
#define IDS_LOCALSTORAGE_HTML 1007
|
||||
#define IDS_LOGO_PNG 1008
|
||||
#define IDS_MEDIA_ROUTER_HTML 1009
|
||||
#define IDS_MENU_ICON_1X_PNG 1010
|
||||
#define IDS_MENU_ICON_2X_PNG 1011
|
||||
#define IDS_OSRTEST_HTML 1012
|
||||
#define IDS_OTHER_TESTS_HTML 1013
|
||||
#define IDS_PDF_HTML 1014
|
||||
#define IDS_PDF_PDF 1015
|
||||
#define IDS_PERFORMANCE_HTML 1016
|
||||
#define IDS_PERFORMANCE2_HTML 1017
|
||||
#define IDS_PREFERENCES_HTML 1018
|
||||
#define IDS_RESPONSE_FILTER_HTML 1019
|
||||
#define IDS_SERVER_HTML 1020
|
||||
#define IDS_TASK_MANAGER_HTML 1021
|
||||
#define IDS_TRANSPARENCY_HTML 1022
|
||||
#define IDS_URLREQUEST_HTML 1023
|
||||
#define IDS_WEBSOCKET_HTML 1024
|
||||
#define IDS_WINDOW_HTML 1025
|
||||
#define IDS_WINDOW_ICON_1X_PNG 1026
|
||||
#define IDS_WINDOW_ICON_2X_PNG 1027
|
||||
#define IDS_XMLHTTPREQUEST_HTML 1028
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
|
@ -15,6 +15,7 @@ int GetResourceId(const char* resource_name) {
|
||||
int id;
|
||||
} resource_map[] = {{"binary_transfer.html", IDS_BINARY_TRANSFER_HTML},
|
||||
{"binding.html", IDS_BINDING_HTML},
|
||||
{"config.html", IDS_CONFIG_HTML},
|
||||
{"dialogs.html", IDS_DIALOGS_HTML},
|
||||
{"draggable.html", IDS_DRAGGABLE_HTML},
|
||||
{"hang.html", IDS_HANG_HTML},
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "tests/cefclient/browser/base_client_handler.h"
|
||||
#include "tests/cefclient/browser/binary_transfer_test.h"
|
||||
#include "tests/cefclient/browser/binding_test.h"
|
||||
#include "tests/cefclient/browser/config_test.h"
|
||||
#include "tests/cefclient/browser/dialog_test.h"
|
||||
#include "tests/cefclient/browser/hang_test.h"
|
||||
#include "tests/cefclient/browser/main_context.h"
|
||||
@ -865,6 +866,9 @@ void CreateMessageHandlers(MessageHandlerSet& handlers) {
|
||||
// Create the binding test handlers.
|
||||
binding_test::CreateMessageHandlers(handlers);
|
||||
|
||||
// Create the config test handlers.
|
||||
config_test::CreateMessageHandlers(handlers);
|
||||
|
||||
// Create the dialog test handlers.
|
||||
dialog_test::CreateMessageHandlers(handlers);
|
||||
|
||||
|
703
tests/cefclient/resources/config.html
Normal file
703
tests/cefclient/resources/config.html
Normal file
@ -0,0 +1,703 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Configuration Test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Verdana, Arial;
|
||||
font-size: 12px;
|
||||
}
|
||||
#message {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
.foot {
|
||||
font-size: 11px;
|
||||
}
|
||||
.mono {
|
||||
font-family: monospace;
|
||||
}
|
||||
.cat_header_0 {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
.cat_header_1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
.cat_header_2 {
|
||||
font-family: Verdana, Arial;
|
||||
}
|
||||
.cat_body {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#temp-message {
|
||||
display: none;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.hr-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.hr-line {
|
||||
border-top: 1px solid black;
|
||||
width: 100%;
|
||||
margin: 0 3px;
|
||||
}
|
||||
.hr-text {
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function onLoad() {
|
||||
if (location.hostname != 'tests') {
|
||||
onCefError(0, 'This page can only be run from tests.');
|
||||
|
||||
// Disable all form elements.
|
||||
var elements = document.getElementById("form").elements;
|
||||
for (var i = 0, element; element = elements[i++]; ) {
|
||||
element.disabled = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
getGlobalConfig();
|
||||
updateFilter();
|
||||
startSubscription();
|
||||
}
|
||||
|
||||
function onUnload() {
|
||||
stopSubscription();
|
||||
}
|
||||
|
||||
function onCefError(code, message) {
|
||||
val = 'ERROR: ' + message;
|
||||
if (code !== 0) {
|
||||
val += ' (' + code + ')';
|
||||
}
|
||||
document.getElementById('message').innerHTML = val + '<br/><br/>';
|
||||
}
|
||||
|
||||
function sendCefQuery(payload, onSuccess, onFailure=onCefError, persistent=false) {
|
||||
// Results in a call to the OnQuery method in config_test.cc
|
||||
return window.cefQuery({
|
||||
request: JSON.stringify(payload),
|
||||
onSuccess: onSuccess,
|
||||
onFailure: onFailure,
|
||||
persistent: persistent
|
||||
});
|
||||
}
|
||||
|
||||
// Request the global configuration.
|
||||
function getGlobalConfig() {
|
||||
sendCefQuery(
|
||||
{name: 'global_config'},
|
||||
(message) => onGlobalConfigMessage(JSON.parse(message)),
|
||||
);
|
||||
}
|
||||
|
||||
// Display the global configuration response.
|
||||
function onGlobalConfigMessage(message) {
|
||||
document.getElementById('global_switches').innerHTML =
|
||||
message.switches !== null ? message.switches.join('<br/>') : '(none)';
|
||||
if (message.strings !== null) {
|
||||
document.getElementById('global_strings').innerHTML = message.strings.join('<br/>');
|
||||
document.getElementById('global_strings_ct').textContent = message.strings.length;
|
||||
}
|
||||
}
|
||||
|
||||
var currentSubscriptionId = null;
|
||||
|
||||
// Subscribe to ongoing message notifications from the native code.
|
||||
function startSubscription() {
|
||||
currentSubscriptionId = sendCefQuery(
|
||||
{name: 'subscribe'},
|
||||
(message) => onSubscriptionMessage(JSON.parse(message)),
|
||||
(code, message) => {
|
||||
onCefError(code, message);
|
||||
currentSubscriptionId = null;
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Unsubscribe from message notifications.
|
||||
function stopSubscription() {
|
||||
if (currentSubscriptionId !== null) {
|
||||
// Results in a call to the OnQueryCanceled method in config_test.cc
|
||||
window.cefQueryCancel(currentSubscriptionId);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a nice timestamp for display purposes.
|
||||
function getNiceTimestamp() {
|
||||
const now = new Date();
|
||||
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
var paused = false;
|
||||
var paused_messages = [];
|
||||
var first_after_pause = false;
|
||||
|
||||
// Toggle whether messages are displayed or queued.
|
||||
function togglePause() {
|
||||
paused = !paused;
|
||||
document.getElementById("pause_button").value = paused ? "Resume" : "Pause";
|
||||
|
||||
if (!paused) {
|
||||
first_after_pause = true;
|
||||
while (paused_messages.length > 0) {
|
||||
onSubscriptionMessage(paused_messages.shift());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doPause() {
|
||||
if (!paused) {
|
||||
togglePause();
|
||||
showTempMessage('Event processing is paused. Click Resume to continue.');
|
||||
}
|
||||
}
|
||||
|
||||
var filter = {}
|
||||
var filtered_ct = 0;
|
||||
var filter_updating = false;
|
||||
|
||||
// Populate |filter| based on form control state.
|
||||
function updateFilter() {
|
||||
if (filter_updating) {
|
||||
// Ignore changes triggered from individual elements while we're updating multiple.
|
||||
return;
|
||||
}
|
||||
|
||||
filter.text = document.getElementById("filter_text").value.trim().toLowerCase();
|
||||
filter.global_prefs = document.getElementById("filter_global_prefs").checked;
|
||||
filter.context_prefs = document.getElementById("filter_context_prefs").checked;
|
||||
filter.context_settings = document.getElementById("filter_context_settings").checked;
|
||||
}
|
||||
|
||||
function doFilter(type, text, global=false) {
|
||||
filter_updating = true;
|
||||
|
||||
document.getElementById("filter_text").value = text;
|
||||
|
||||
var checked = '';
|
||||
if (type === 'preference') {
|
||||
checked = global ? 'filter_global_prefs' : 'filter_context_prefs';
|
||||
} else if (type === 'setting') {
|
||||
checked = 'filter_context_settings';
|
||||
}
|
||||
|
||||
['filter_global_prefs', 'filter_context_prefs', 'filter_context_settings'].forEach(function(id) {
|
||||
document.getElementById(id).checked = id === checked;
|
||||
});
|
||||
|
||||
filter_updating = false;
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
function doFilterReset() {
|
||||
filter_updating = true;
|
||||
document.getElementById("filtered_ct").textContent = 0;
|
||||
document.getElementById("filter_text").value = '';
|
||||
document.getElementById("filter_global_prefs").checked = true;
|
||||
document.getElementById("filter_context_prefs").checked = true;
|
||||
document.getElementById("filter_context_settings").checked = true;
|
||||
filter_updating = false;
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
// Returns true if the message should be displayed based on the current filter settings.
|
||||
function passesFilter(message) {
|
||||
if (message.type === 'preference') {
|
||||
if (message.global) {
|
||||
if (!filter.global_prefs) {
|
||||
return false;
|
||||
}
|
||||
} else if (!filter.context_prefs) {
|
||||
return false;
|
||||
}
|
||||
} else if (message.type === 'setting' && !filter.context_settings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter.text.length > 0) {
|
||||
var check_text = JSON.stringify(message).toLowerCase();
|
||||
if (message.type === 'setting') {
|
||||
check_text += ' ' + getSettingTypeLabel(message.content_type).toLowerCase();
|
||||
}
|
||||
if (check_text.indexOf(filter.text) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Match the cef_value_type_t values from include/internal/cef_types.h
|
||||
const value_types = [
|
||||
'INVALID',
|
||||
'NULL',
|
||||
'BOOL',
|
||||
'INT',
|
||||
'DOUBLE',
|
||||
'STRING',
|
||||
'BINARY',
|
||||
'DICTIONARY',
|
||||
'LIST',
|
||||
]
|
||||
|
||||
function getValueType(index) {
|
||||
if (index < 0 || index >= value_types.length) {
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
return value_types[index];
|
||||
}
|
||||
|
||||
// Match the cef_content_setting_types_t values from include/internal/cef_types_content_settings.h
|
||||
const setting_types = [
|
||||
"COOKIES",
|
||||
"IMAGES",
|
||||
"JAVASCRIPT",
|
||||
"POPUPS",
|
||||
"GEOLOCATION",
|
||||
"NOTIFICATIONS",
|
||||
"AUTO_SELECT_CERTIFICATE",
|
||||
"MIXEDSCRIPT",
|
||||
"MEDIASTREAM_MIC",
|
||||
"MEDIASTREAM_CAMERA",
|
||||
"PROTOCOL_HANDLERS",
|
||||
"DEPRECATED_PPAPI_BROKER",
|
||||
"AUTOMATIC_DOWNLOADS",
|
||||
"MIDI_SYSEX",
|
||||
"SSL_CERT_DECISIONS",
|
||||
"PROTECTED_MEDIA_IDENTIFIER",
|
||||
"APP_BANNER",
|
||||
"SITE_ENGAGEMENT",
|
||||
"DURABLE_STORAGE",
|
||||
"USB_CHOOSER_DATA",
|
||||
"BLUETOOTH_GUARD",
|
||||
"BACKGROUND_SYNC",
|
||||
"AUTOPLAY",
|
||||
"IMPORTANT_SITE_INFO",
|
||||
"PERMISSION_AUTOBLOCKER_DATA",
|
||||
"ADS",
|
||||
"ADS_DATA",
|
||||
"MIDI",
|
||||
"PASSWORD_PROTECTION",
|
||||
"MEDIA_ENGAGEMENT",
|
||||
"SOUND",
|
||||
"CLIENT_HINTS",
|
||||
"SENSORS",
|
||||
"DEPRECATED_ACCESSIBILITY_EVENTS",
|
||||
"PAYMENT_HANDLER",
|
||||
"USB_GUARD",
|
||||
"BACKGROUND_FETCH",
|
||||
"INTENT_PICKER_DISPLAY",
|
||||
"IDLE_DETECTION",
|
||||
"SERIAL_GUARD",
|
||||
"SERIAL_CHOOSER_DATA",
|
||||
"PERIODIC_BACKGROUND_SYNC",
|
||||
"BLUETOOTH_SCANNING",
|
||||
"HID_GUARD",
|
||||
"HID_CHOOSER_DATA",
|
||||
"WAKE_LOCK_SCREEN",
|
||||
"WAKE_LOCK_SYSTEM",
|
||||
"LEGACY_COOKIE_ACCESS",
|
||||
"FILE_SYSTEM_WRITE_GUARD",
|
||||
"NFC",
|
||||
"BLUETOOTH_CHOOSER_DATA",
|
||||
"CLIPBOARD_READ_WRITE",
|
||||
"CLIPBOARD_SANITIZED_WRITE",
|
||||
"SAFE_BROWSING_URL_CHECK_DATA",
|
||||
"VR",
|
||||
"AR",
|
||||
"FILE_SYSTEM_READ_GUARD",
|
||||
"STORAGE_ACCESS",
|
||||
"CAMERA_PAN_TILT_ZOOM",
|
||||
"WINDOW_MANAGEMENT",
|
||||
"INSECURE_PRIVATE_NETWORK",
|
||||
"LOCAL_FONTS",
|
||||
"PERMISSION_AUTOREVOCATION_DATA",
|
||||
"FILE_SYSTEM_LAST_PICKED_DIRECTORY",
|
||||
"DISPLAY_CAPTURE",
|
||||
"FILE_SYSTEM_ACCESS_CHOOSER_DATA",
|
||||
"FEDERATED_IDENTITY_SHARING",
|
||||
"JAVASCRIPT_JIT",
|
||||
"HTTP_ALLOWED",
|
||||
"FORMFILL_METADATA",
|
||||
"DEPRECATED_FEDERATED_IDENTITY_ACTIVE_SESSION",
|
||||
"AUTO_DARK_WEB_CONTENT",
|
||||
"REQUEST_DESKTOP_SITE",
|
||||
"FEDERATED_IDENTITY_API",
|
||||
"NOTIFICATION_INTERACTIONS",
|
||||
"REDUCED_ACCEPT_LANGUAGE",
|
||||
"NOTIFICATION_PERMISSION_REVIEW",
|
||||
"PRIVATE_NETWORK_GUARD",
|
||||
"PRIVATE_NETWORK_CHOOSER_DATA",
|
||||
"FEDERATED_IDENTITY_IDENTITY_PROVIDER_SIGNIN_STATUS",
|
||||
"REVOKED_UNUSED_SITE_PERMISSIONS",
|
||||
"TOP_LEVEL_STORAGE_ACCESS",
|
||||
"FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION",
|
||||
"FEDERATED_IDENTITY_IDENTITY_PROVIDER_REGISTRATION",
|
||||
"ANTI_ABUSE",
|
||||
"THIRD_PARTY_STORAGE_PARTITIONING",
|
||||
"HTTPS_ENFORCED",
|
||||
"ALL_SCREEN_CAPTURE",
|
||||
"COOKIE_CONTROLS_METADATA",
|
||||
"TPCD_HEURISTICS_GRANTS",
|
||||
"TPCD_METADATA_GRANTS",
|
||||
"TPCD_TRIAL",
|
||||
"TOP_LEVEL_TPCD_TRIAL",
|
||||
"TOP_LEVEL_TPCD_ORIGIN_TRIAL",
|
||||
"AUTO_PICTURE_IN_PICTURE",
|
||||
"FILE_SYSTEM_ACCESS_EXTENDED_PERMISSION",
|
||||
"FILE_SYSTEM_ACCESS_RESTORE_PERMISSION",
|
||||
"CAPTURED_SURFACE_CONTROL",
|
||||
"SMART_CARD_GUARD",
|
||||
"SMART_CARD_DATA",
|
||||
"WEB_PRINTING",
|
||||
"AUTOMATIC_FULLSCREEN",
|
||||
"SUB_APP_INSTALLATION_PROMPTS",
|
||||
"SPEAKER_SELECTION",
|
||||
"DIRECT_SOCKETS",
|
||||
"KEYBOARD_LOCK",
|
||||
"POINTER_LOCK",
|
||||
"REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS",
|
||||
"TRACKING_PROTECTION",
|
||||
"DISPLAY_MEDIA_SYSTEM_AUDIO",
|
||||
"JAVASCRIPT_OPTIMIZER",
|
||||
"STORAGE_ACCESS_HEADER_ORIGIN_TRIAL",
|
||||
"HAND_TRACKING",
|
||||
"WEB_APP_INSTALLATION",
|
||||
"DIRECT_SOCKETS_PRIVATE_NETWORK_ACCESS",
|
||||
"LEGACY_COOKIE_SCOPE",
|
||||
"ARE_SUSPICIOUS_NOTIFICATIONS_ALLOWLISTED_BY_USER",
|
||||
"CONTROLLED_FRAME",
|
||||
];
|
||||
|
||||
function getSettingType(index) {
|
||||
if (index < 0 || index >= setting_types.length) {
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
return setting_types[index];
|
||||
}
|
||||
|
||||
function getSettingTypeLabel(type) {
|
||||
return getSettingType(type) + ' (' + type + ')'
|
||||
}
|
||||
|
||||
function makeDetails(summaryHTML, summaryClass, contentHTML, contentClass, contentId=null, open=false) {
|
||||
const newDetails = document.createElement('details');
|
||||
if (open) {
|
||||
newDetails.open = true;
|
||||
}
|
||||
|
||||
const newSummary = document.createElement('summary');
|
||||
newSummary.innerHTML = summaryHTML;
|
||||
if (summaryClass !== null) {
|
||||
newSummary.className = summaryClass;
|
||||
}
|
||||
newDetails.append(newSummary);
|
||||
|
||||
const newContent = document.createElement('p');
|
||||
newContent.innerHTML = contentHTML
|
||||
if (contentClass !== null) {
|
||||
newContent.className = contentClass;
|
||||
}
|
||||
if (contentId !== null) {
|
||||
newContent.id = contentId;
|
||||
}
|
||||
newDetails.append(newContent);
|
||||
|
||||
const newP = document.createElement('p');
|
||||
newP.append(newDetails);
|
||||
|
||||
return newP;
|
||||
}
|
||||
|
||||
function makeValueExample(value, value_type) {
|
||||
code = '\n// Create a CefValue object programmatically:\n' +
|
||||
'auto value = CefValue::Create();\n';
|
||||
if (value === null || getValueType(value_type) == 'NULL') {
|
||||
code += 'value->SetNull();\n';
|
||||
} else if (typeof value === 'boolean' || getValueType(value_type) == 'BOOL') {
|
||||
code += 'value->SetBool(' + (value ? 'true' : 'false') + ');\n';
|
||||
} else if (Number.isInteger(value) || getValueType(value_type) == 'INT') {
|
||||
code += 'value->SetInt(' + value + ');\n';
|
||||
} else if (typeof value === 'number' || getValueType(value_type) == 'DOUBLE') {
|
||||
code += 'value->SetDouble(' + value + ');\n';
|
||||
} else if (typeof value === 'string' || getValueType(value_type) == 'STRING') {
|
||||
code += 'value->SetString("' + value + '");\n';
|
||||
} else if (Array.isArray(value) || getValueType(value_type) == 'LIST') {
|
||||
code += 'auto listValue = CefListValue::Create();\n';
|
||||
if (value.length > 0) {
|
||||
code += '\n// TODO: Populate |listValue| using CefListValue::Set* methods.\n\n';
|
||||
}
|
||||
code += 'value->SetList(listValue);\n';
|
||||
if (value.length > 0) {
|
||||
code += '\n// ALTERNATELY: Create a CefValue object by parsing a JSON string:\n' +
|
||||
'auto value = CefParseJSON("[ ... ]", JSON_PARSER_RFC);\n';
|
||||
}
|
||||
} else if (typeof value === 'object' || getValueType(value_type) == 'DICTIONARY') {
|
||||
code += 'auto dictValue = CefDictionaryValue::Create();\n';
|
||||
const has_value = Object.keys(value).length > 0;
|
||||
if (has_value) {
|
||||
code += '\n// TODO: Populate |dictValue| using CefDictionaryValue::Set* methods.\n\n';
|
||||
}
|
||||
code += 'value->SetDictionary(dictValue);\n';
|
||||
if (has_value) {
|
||||
code += '\n// ALTERNATELY: Create a CefValue object by parsing a JSON string:\n' +
|
||||
'auto value = CefParseJSON("{ ... }", JSON_PARSER_RFC);\n';
|
||||
}
|
||||
} else {
|
||||
code += '\n//TODO: Populate |value|.\n\n';
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
function makeCopyLink(elem_id) {
|
||||
return '<a href="#" onMouseDown="copyToClipboard(\'' + elem_id + '\')" onClick="return false">[copy to clipboard]</a>';
|
||||
}
|
||||
|
||||
function makeContent(elem_id, content) {
|
||||
const content_id = 'cn-' + elem_id;
|
||||
return makeDetails('Content ' + makeCopyLink(content_id), 'cat_header_2', content, 'cat_body', content_id, true);
|
||||
}
|
||||
|
||||
function makeHR(label) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'hr-container';
|
||||
const line1 = document.createElement('div');
|
||||
line1.className = 'hr-line';
|
||||
container.append(line1);
|
||||
const text = document.createElement('span');
|
||||
text.className = 'hr-text';
|
||||
text.innerHTML = label;
|
||||
container.append(text);
|
||||
const line2 = document.createElement('div');
|
||||
line2.className = 'hr-line';
|
||||
container.append(line2);
|
||||
return container;
|
||||
}
|
||||
|
||||
function makeCodeExample(elem_id, message) {
|
||||
const example_id = 'ex-' + elem_id;
|
||||
var code = '// Code must be executed on the browser process UI thread.\n\n';
|
||||
|
||||
if (message.type === "preference") {
|
||||
if (message.global) {
|
||||
code += 'auto pref_manager = CefPreferenceManager::GetGlobalPreferenceManager();\n';
|
||||
} else {
|
||||
code += '// |browser| is an existing CefBrowser instance.\n' +
|
||||
'auto pref_manager = browser->GetHost()->GetRequestContext();\n';
|
||||
}
|
||||
code += makeValueExample(message.value, message.value_type) + '\n' +
|
||||
'CefString error;\n' +
|
||||
'bool success = pref_manager->SetPreference("' + message.name + '", value, error);\n' +
|
||||
'if (!success) {\n' +
|
||||
' // TODO: Use |error| to diagnose the failure.\n' +
|
||||
'}\n';
|
||||
} else if (message.type === "setting") {
|
||||
const type = getSettingType(message.content_type);
|
||||
const content_type = type !== 'UNKNOWN' ? 'CEF_CONTENT_SETTING_TYPE_' + type : message.content_type;
|
||||
|
||||
code += '// |browser| is an existing CefBrowser instance.\n' +
|
||||
'auto context = browser->GetHost()->GetRequestContext();\n' +
|
||||
makeValueExample(message.value, message.value_type) + '\n' +
|
||||
'context->SetWebsiteSetting("' + message.requesting_url + '", "' + message.top_level_url +
|
||||
'", '+ content_type +', value);\n';
|
||||
}
|
||||
|
||||
return makeDetails('C++ Code Example ' + makeCopyLink(example_id), 'cat_header_2', code, 'cat_body', example_id, false);
|
||||
}
|
||||
|
||||
var message_ct = 0;
|
||||
|
||||
// A new message has arrived. It may be queued, filtered out or displayed.
|
||||
function onSubscriptionMessage(message) {
|
||||
if (paused) {
|
||||
// Queue the message until the user clicks Resume.
|
||||
message.timestamp = getNiceTimestamp();
|
||||
paused_messages.push(message);
|
||||
document.getElementById("pause_button").value = 'Resume (' + paused_messages.length + ')';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passesFilter(message)) {
|
||||
// Filter out the message.
|
||||
filtered_ct++;
|
||||
document.getElementById("filtered_ct").innerHTML = filtered_ct;
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the arrival timestamp for queued messages.
|
||||
var timestamp;
|
||||
if (message.timestamp) {
|
||||
timestamp = message.timestamp;
|
||||
delete message.timestamp;
|
||||
} else {
|
||||
timestamp = getNiceTimestamp();
|
||||
}
|
||||
|
||||
// Display the message.
|
||||
var label = timestamp + ': ';
|
||||
var content = 'value_type=' + getValueType(message.value_type);
|
||||
var search = '';
|
||||
var filter = '';
|
||||
|
||||
if (message.type === "preference") {
|
||||
label += 'Preference (' + (message.global ? 'Global' : 'Profile') +
|
||||
') <span class="mono">' + message.name + '</span>';
|
||||
search = '%5C%22' + message.name + '%5C%22';
|
||||
filter = "'preference', '" + message.name + "', " + (message.global ? 'true' : 'false');
|
||||
} else if (message.type === "setting") {
|
||||
label += 'Setting <span class="mono">' + getSettingTypeLabel(message.content_type) + '</span>';
|
||||
const setting_type = getSettingType(message.content_type);
|
||||
search = 'ContentSettingsType::' + setting_type;
|
||||
filter = "'setting', '" + setting_type + "'";
|
||||
content = 'requesting_url=' + message.requesting_url +
|
||||
'\ntop_level_url=' + message.top_level_url +
|
||||
'\n' + content;
|
||||
}
|
||||
content += '\nvalue=' + JSON.stringify(message.value, null, 1);
|
||||
label += ' <a href="#" onMouseDown="doFilter(' + filter + ')" onClick="return false">[filter]</a>' +
|
||||
' <a href="https://source.chromium.org/search?q=' + search + '" target="_blank">[search 🔗]</a>';
|
||||
|
||||
const messages = document.getElementById('messages');
|
||||
|
||||
if (first_after_pause) {
|
||||
messages.prepend(makeHR('RESUMED'));
|
||||
first_after_pause = false;
|
||||
}
|
||||
|
||||
const elem_id = message_ct++;
|
||||
const newDetails = makeDetails(label, null, makeContent(elem_id, content).outerHTML +
|
||||
makeCodeExample(elem_id, message).outerHTML, 'cat_body');
|
||||
messages.prepend(newDetails);
|
||||
}
|
||||
|
||||
// Clear filter count and displayed/pending messages.
|
||||
function doClear() {
|
||||
filtered_ct = 0;
|
||||
document.getElementById("filtered_ct").textContent = 0;
|
||||
document.getElementById('messages').innerHTML = '';
|
||||
if (paused) {
|
||||
paused_messages = [];
|
||||
document.getElementById("pause_button").value = 'Resume';
|
||||
}
|
||||
message_ct = 0;
|
||||
}
|
||||
|
||||
function showTempMessage(msg) {
|
||||
const element = document.getElementById("temp-message");
|
||||
element.innerHTML = msg;
|
||||
element.style.display = "block";
|
||||
|
||||
setTimeout(function() {
|
||||
element.style.display = "none";
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function copyToClipboard(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make all parent details nodes are open, otherwise nothing will be copied to the clipboard.
|
||||
var parent = element.parentNode;
|
||||
while (parent) {
|
||||
if (parent.nodeName === 'DETAILS') {
|
||||
if (!parent.open) {
|
||||
parent.open = true;
|
||||
}
|
||||
}
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(element.outerText)
|
||||
.then(() => {
|
||||
showTempMessage('Text copied to clipboard.');
|
||||
})
|
||||
.catch(err => {
|
||||
showTempMessage('Failed to copy text to clipboard!');
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body bgcolor="white" onload="onLoad()" onunload="onUnload()">
|
||||
<div id="message"></div>
|
||||
<details open>
|
||||
<summary class="cat_header_0">Startup configuration</summary>
|
||||
<p class="desc">
|
||||
This section displays the global configuration (Chrome Variations) that was applied at startup.
|
||||
Chrome Variations can be configured via chrome://flags <sup>[*]</sup>, via the below command-line switches, or via field trials (disabled in Official builds).
|
||||
The Active Variations section below is the human-readable equivalent of the "Active Variations" section of chrome://version.
|
||||
See <a href="https://developer.chrome.com/docs/web-platform/chrome-variations" target="_blank">Chrome Variations docs</a> for background.
|
||||
</p>
|
||||
<p class="foot">
|
||||
* Flags are stored in the global <span class="mono">browser.enabled_labs_experiments</span> preference.
|
||||
</p>
|
||||
<p class="cat_header_1">Command-Line Switches:</p>
|
||||
<p class="cat_body" id="global_switches"></p>
|
||||
<details>
|
||||
<summary class="cat_header_1">Active Variations (<span id="global_strings_ct">0</span>)</summary>
|
||||
<p class="cat_body" id="global_strings"></p>
|
||||
</details>
|
||||
</details>
|
||||
<br/>
|
||||
<details open>
|
||||
<summary class="cat_header_0">Runtime configuration</summary>
|
||||
<p class="desc">
|
||||
This section displays preference and site settings changes that occur during runtime.
|
||||
Chromium stores both global and Profile-specific preferences.
|
||||
See <a href="https://www.chromium.org/developers/design-documents/preferences/" target="_blank">Preferences docs</a> for background.
|
||||
To view a snapshot of all preferences go <a href="https://tests/preferences#advanced">here</a> instead.
|
||||
</p>
|
||||
<p id="filter">
|
||||
<form id="form">
|
||||
Text Contains: <input type="text" id="filter_text"/> <input type="button" onclick="updateFilter();" value="Apply"/>
|
||||
<br/>Show: <input type="checkbox" id="filter_global_prefs" onChange="updateFilter()" checked /> Global preferences
|
||||
<input type="checkbox" id="filter_context_prefs" onChange="updateFilter()" checked /> Profile-specific preferences
|
||||
<input type="checkbox" id="filter_context_settings" onChange="updateFilter()" checked /> Site settings <sup>[*]</sup>
|
||||
<br/><input type="button" id="clear_button" onclick="doClear()" value="Clear"/>
|
||||
<input type="button" id="pause_button" onclick="togglePause()" value="Pause"/> <sup>[**]</sup> Filtered out: <span id="filtered_ct">0</span>
|
||||
<input type="button" id="reset_button" onclick="doFilterReset()" value="Reset"/>
|
||||
<p class="foot">
|
||||
* Site settings are stored in the Profile-specific <span class="mono">profile.content_settings</span> preference and can be modified via chrome://settings/content.
|
||||
<br/>** Events will not be displayed or filtered out while processing is paused.
|
||||
</p>
|
||||
</form>
|
||||
</p>
|
||||
<div id="messages" onMouseDown="doPause()"></div>
|
||||
<div id="temp-message"></div>
|
||||
</details>
|
||||
</body>
|
||||
</html>
|
@ -10,6 +10,7 @@
|
||||
<li><a href="https://jigsaw.w3.org/HTTP/Basic/">Authentication (Basic)</a> - credentials returned via GetAuthCredentials</li>
|
||||
<li><a href="https://jigsaw.w3.org/HTTP/Digest/">Authentication (Digest)</a> - credentials returned via GetAuthCredentials</li>
|
||||
<li><a href="binary_transfer">Binary vs String Transfer Benchmark</a></li>
|
||||
<li><a href="config">Chrome Configuration</a></li>
|
||||
<li><a href="http://html5advent2011.digitpaint.nl/3/index.html">Cursors</a></li>
|
||||
<li><a href="dialogs">Dialogs</a></li>
|
||||
<li><a href="http://html5demos.com/drag">Drag & Drop</a></li>
|
||||
|
@ -13,8 +13,12 @@
|
||||
|
||||
<script>
|
||||
function setup() {
|
||||
if (location.hostname == 'tests' || location.hostname == 'localhost')
|
||||
if (location.hostname === 'tests' || location.hostname === 'localhost') {
|
||||
if (location.hash === '#advanced') {
|
||||
toggleView();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
alert('This page can only be run from tests or localhost.');
|
||||
|
||||
|
@ -31,6 +31,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
IDS_BINARY_TRANSFER_HTML BINARY "tests\\cefclient\\resources\\binary_transfer.html"
|
||||
IDS_BINDING_HTML BINARY "tests\\cefclient\\resources\\binding.html"
|
||||
IDS_CONFIG_HTML BINARY "tests\\cefclient\\resources\\config.html"
|
||||
IDS_DIALOGS_HTML BINARY "tests\\cefclient\\resources\\dialogs.html"
|
||||
IDS_DRAGGABLE_HTML BINARY "tests\\cefclient\\resources\\draggable.html"
|
||||
IDS_HANG_HTML BINARY "tests\\cefclient\\resources\\hang.html"
|
||||
|
Reference in New Issue
Block a user