Files
servo/resources/resource_protocol/config.html
Ashwin Naren 232d434701 Add servo:config page (#40324)
Similar to about:config from firefox. All preferences are editable;
editing them mid-runtime is not guaranteed to cause any effects. This is
separate from servo:preferences, which selectively groups and exposes
preferences. This probably would become more useful if/when preferences
become persistent.

<img width="1136" height="880" alt="Screenshot 2025-10-31 at 10 19
57 PM"
src="https://github.com/user-attachments/assets/2ef759d8-06a4-457f-b9df-331cc3525338"
/>


Followup work:
- Remove `getStringPreference`, `getIntPreference`, and
`getBoolPreference`. Using `getPreference` and `preferenceType` is more
flexible.
- Make more of these config options work on the fly.
- Allow for reverting config options.

---------

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2026-02-09 17:31:59 +00:00

253 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Advanced Config</title>
<link rel="stylesheet" href="resource:///config.css">
</head>
<body>
<input class="search" type="text" placeholder="Search for a preference…" aria-label="Search preferences" oninput="populate(this.value)">
<table></table>
<script>
// Helper to create a TD with class and optional text
function td(cls, text) {
const cell = document.createElement("td");
if (cls !== undefined) {
cell.className = cls;
}
if (text !== undefined) {
cell.textContent = text;
}
return cell;
}
class PrefBoolRow extends HTMLTableRowElement {
connectedCallback() {
// Attributes are provided via data-*
const pref = this.dataset.pref;
const defaultValue = this.dataset.default === "true";
let value = this.dataset.value === "true";
const nameCell = td("pref-name", pref);
const valueCell = td("value", String(value));
valueCell.id = `value-${pref}`;
if (value !== defaultValue) valueCell.classList.add("default");
const switcher = td("switcher");
const btn = document.createElement("button");
btn.className = "switch-button";
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
</svg>`;
btn.onclick = () => {
value = !value;
valueCell.classList.toggle("default", value !== defaultValue)
navigator.servo.setBoolPreference(pref, value);
valueCell.textContent = String(value);
};
switcher.appendChild(btn);
this.appendChild(nameCell);
this.appendChild(valueCell);
this.appendChild(switcher);
}
}
// Unified row for int and string preferences
class PrefValueRow extends HTMLTableRowElement {
connectedCallback() {
const pref = this.dataset.pref;
const ty = this.dataset.type; // "i64" or "String"
const nameCell = td("pref-name", pref);
const valueCell = document.createElement("td");
valueCell.className = "value";
const valueSpan = document.createElement("span");
valueSpan.className = "value-entry";
valueSpan.id = `value-${pref}`;
const input = document.createElement("input");
input.className = "value-editor hidden";
input.id = `input-${pref}`;
// Per-type state and behavior
let defaultValue;
let value;
let saveFn;
if (ty === "i64") {
defaultValue = parseInt(this.dataset.default);
value = parseInt(this.dataset.value);
valueSpan.textContent = value;
input.type = "number";
input.step = "1";
saveFn = () => {
const parsed = parseInt(input.value);
if (Number.isNaN(parsed)) {
input.setCustomValidity("Please enter a valid integer.");
input.reportValidity();
return false;
}
navigator.servo.setIntPreference(pref, parsed);
value = parsed;
valueSpan.textContent = value;
valueCell.classList.toggle("default", value !== defaultValue)
return true;
};
} else {
// string, no validation needed
defaultValue = this.dataset.default ?? "";
value = this.dataset.value ?? "";
valueSpan.textContent = value;
input.type = "text";
saveFn = () => {
const newValue = input.value;
navigator.servo.setStringPreference(pref, newValue);
value = newValue;
valueSpan.textContent = value;
valueCell.classList.toggle("default", value !== defaultValue)
return true;
};
}
valueSpan.classList.toggle("default", value !== defaultValue)
valueCell.appendChild(valueSpan);
valueCell.appendChild(input);
const switcher = td("switcher");
const editBtn = document.createElement("button");
editBtn.className = "switch-button";
editBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" />
</svg>`;
const saveBtn = document.createElement("button");
saveBtn.className = "switch-button hidden";
saveBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>`;
const cancelEdit = () => {
// Revert input view and value without saving
input.classList.add("hidden");
valueSpan.classList.remove("hidden");
input.value = String(value);
editBtn.classList.remove("hidden");
saveBtn.classList.add("hidden");
input.setCustomValidity("");
};
editBtn.onclick = () => {
valueSpan.classList.add("hidden");
input.classList.remove("hidden");
input.value = String(value);
editBtn.classList.add("hidden");
saveBtn.classList.remove("hidden");
input.focus();
input.setSelectionRange(0, input.value.length);
};
saveBtn.onclick = () => {
const ok = saveFn();
if (!ok) return; // keep editing on validation failure
valueSpan.classList.remove("hidden");
input.classList.add("hidden");
editBtn.classList.remove("hidden");
saveBtn.classList.add("hidden");
};
// Enter to save, Escape to cancel
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
saveBtn.click();
} else if (e.key === "Escape") {
e.preventDefault();
cancelEdit();
}
});
switcher.appendChild(editBtn);
switcher.appendChild(saveBtn);
this.appendChild(nameCell);
this.appendChild(valueCell);
this.appendChild(switcher);
}
}
class PrefUnknownRow extends HTMLTableRowElement {
connectedCallback() {
const pref = this.dataset.pref;
const value = this.dataset.value ?? "";
this.appendChild(td("pref-name", pref));
this.appendChild(td("value", value));
}
}
customElements.define("pref-bool-row", PrefBoolRow, { extends: "tr" });
customElements.define("pref-value-row", PrefValueRow, { extends: "tr" });
customElements.define("pref-unknown-row", PrefUnknownRow, { extends: "tr" });
let all = navigator.servo.preferenceList();
all.sort();
const ALL = all;
function populate(filter) {
const table = document.querySelector("table");
table.innerHTML = "";
let prefs = [];
if (filter === "" || filter === null || filter === undefined) {
prefs = ALL;
} else {
// TODO: improve filtering
prefs = ALL.filter(pref => {
let stripped_pref = pref.replaceAll("_", "");
let stripped_filter = filter.replaceAll("_", "").replaceAll(" ", "").toLowerCase();
if (stripped_pref.includes(stripped_filter)) {
prefs.push(pref);
}
});
}
for (let pref of prefs) {
let ty = navigator.servo.preferenceType(pref);
let value = navigator.servo.getPreference(pref);
let defaultValue = navigator.servo.defaultPreferenceValue(pref);
if (ty === "bool") {
const row = document.createElement("tr", { is: "pref-bool-row" });
row.setAttribute("data-pref", pref);
row.setAttribute("data-value", String(value));
row.setAttribute("data-default", String(defaultValue));
table.appendChild(row);
} else if (ty === "i64") {
const row = document.createElement("tr", { is: "pref-value-row" });
row.setAttribute("data-pref", pref);
row.setAttribute("data-type", "i64");
row.setAttribute("data-value", String(value));
row.setAttribute("data-default", String(defaultValue));
table.appendChild(row);
} else if (ty === "String") {
const row = document.createElement("tr", { is: "pref-value-row" });
row.setAttribute("data-pref", pref);
row.setAttribute("data-type", "String");
row.setAttribute("data-value", String(value));
row.setAttribute("data-default", String(defaultValue));
table.appendChild(row);
} else {
const row = document.createElement("tr", { is: "pref-unknown-row" });
row.setAttribute("data-pref", pref);
row.setAttribute("data-value", String(value));
table.appendChild(row);
}
}
}
populate();
</script>
</body>
</html>