Files
cef/tests/cefclient/resources/config.html
Marshall Greenblatt 549e8fe05c 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.
2025-03-10 15:50:46 +00:00

704 lines
24 KiB
HTML

<!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 &#x1F517]</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>