Bug 1501410 - Part 2 - Add support for recovering deleted preferences. r=bgrins

This updates the user interface for adding new preferences to be closer to the mockup, and makes deleted preferences more similar to rows that are not added yet. This will later make it simpler to react to external changes in the underlying data.

Differential Revision: https://phabricator.services.mozilla.com/D15945

--HG--
extra : rebase_source : dbd4bc08ea1a75ae534dce327103f854ef84f8dc
This commit is contained in:
Paolo Amadini 2019-01-15 23:48:11 +00:00
parent 9da6d02ee2
commit 1c6a8a588a
5 changed files with 149 additions and 106 deletions

View File

@ -63,11 +63,17 @@
padding-inline-start: 30px;
}
#prefs > tr.deleted > td.cell-name {
font-weight: bold;
opacity: 0.4;
}
.cell-value {
word-break: break-all;
}
td.cell-value > form > input {
td.cell-value > form > input[type="text"],
td.cell-value > form > input[type="number"] {
-moz-appearance: textfield;
width: 100%;
box-sizing: border-box;

View File

@ -27,6 +27,7 @@ let gPrefInEdit = null;
class PrefRow {
constructor(name) {
this.name = name;
this.value = true;
this.refreshValue();
this.editing = false;
@ -36,11 +37,15 @@ class PrefRow {
}
refreshValue() {
this.hasDefaultValue = prefHasDefaultValue(this.name);
this.hasUserValue = Services.prefs.prefHasUserValue(this.name);
this.hasDefaultValue = this.hasUserValue ? prefHasDefaultValue(this.name)
: true;
this.isLocked = Services.prefs.prefIsLocked(this.name);
// If this preference has been deleted, we keep its last known value.
if (!this.exists) {
return;
}
try {
// This can throw for locked preferences without a default value.
this.value = Preferences.get(this.name);
@ -57,6 +62,14 @@ class PrefRow {
}
}
get type() {
return this.value.constructor.name;
}
get exists() {
return this.hasDefaultValue || this.hasUserValue;
}
_setupElement() {
this.element.textContent = "";
let nameCell = document.createElement("td");
@ -88,9 +101,10 @@ class PrefRow {
refreshElement() {
this.element.classList.toggle("has-user-value", !!this.hasUserValue);
this.element.classList.toggle("locked", !!this.isLocked);
if (!this.editing) {
this.element.classList.toggle("deleted", !this.exists);
if (this.exists && !this.editing) {
this.valueCell.textContent = this.value;
if (this.value.constructor.name == "Boolean") {
if (this.type == "Boolean") {
document.l10n.setAttributes(this.editButton, "about-config-pref-toggle");
this.editButton.className = "button-toggle";
} else {
@ -106,20 +120,52 @@ class PrefRow {
let form = document.createElement("form");
form.addEventListener("submit", event => event.preventDefault());
form.id = "form-edit";
this.inputField = document.createElement("input");
this.inputField.value = this.value;
if (this.value.constructor.name == "Number") {
this.inputField.type = "number";
this.inputField.required = true;
this.inputField.min = -2147483648;
this.inputField.max = 2147483647;
if (this.editing) {
this.inputField = document.createElement("input");
this.inputField.value = this.value;
if (this.type == "Number") {
this.inputField.type = "number";
this.inputField.required = true;
this.inputField.min = -2147483648;
this.inputField.max = 2147483647;
} else {
this.inputField.type = "text";
}
form.appendChild(this.inputField);
document.l10n.setAttributes(this.editButton, "about-config-pref-save");
this.editButton.className = "primary button-save";
} else {
this.inputField.type = "text";
delete this.inputField;
for (let type of ["Boolean", "Number", "String"]) {
let radio = document.createElement("input");
radio.type = "radio";
radio.name = "type";
radio.value = type;
radio.checked = this.type == type;
form.appendChild(radio);
let radioLabel = document.createElement("span");
radioLabel.textContent = type;
form.appendChild(radioLabel);
}
form.addEventListener("click", event => {
if (event.target.name != "type") {
return;
}
let type = event.target.value;
if (this.type != type) {
if (type == "Boolean") {
this.value = true;
} else if (type == "Number") {
this.value = 0;
} else {
this.value = "";
}
}
});
document.l10n.setAttributes(this.editButton, "about-config-pref-add");
this.editButton.className = "button-add";
}
form.appendChild(this.inputField);
this.valueCell.appendChild(form);
document.l10n.setAttributes(this.editButton, "about-config-pref-save");
this.editButton.className = "primary button-save";
this.editButton.setAttribute("form", "form-edit");
}
this.editButton.disabled = this.isLocked;
@ -154,7 +200,7 @@ class PrefRow {
}
save() {
if (this.value.constructor.name == "Number") {
if (this.type == "Number") {
if (!this.inputField.reportValidity()) {
return;
}
@ -219,14 +265,13 @@ function loadPrefs() {
pref.refreshValue();
pref.refreshElement();
pref.editButton.focus();
} else if (button.classList.contains("add-true")) {
addNewPref(prefRow.firstChild.innerHTML, true);
} else if (button.classList.contains("add-false")) {
addNewPref(prefRow.firstChild.innerHTML, false);
} else if (button.classList.contains("add-Number") ||
button.classList.contains("add-String")) {
addNewPref(prefRow.firstChild.innerHTML,
button.classList.contains("add-Number") ? 0 : "").edit();
} else if (button.classList.contains("button-add")) {
Preferences.set(pref.name, pref.value);
pref.refreshValue();
pref.refreshElement();
if (pref.type != "Boolean") {
pref.edit();
}
} else if (button.classList.contains("button-toggle")) {
Services.prefs.setBoolPref(pref.name, !pref.value);
pref.refreshValue();
@ -236,9 +281,11 @@ function loadPrefs() {
} else if (button.classList.contains("button-save")) {
pref.save();
} else {
pref.editing = false;
Services.prefs.clearUserPref(pref.name);
gExistingPrefs.delete(pref.name);
prefRow.remove();
pref.refreshValue();
pref.refreshElement();
}
});
@ -251,58 +298,21 @@ function filterPrefs() {
}
let substring = document.getElementById("search").value.trim();
document.getElementById("prefs").textContent = "";
let prefArray = [...gExistingPrefs.values()];
if (substring) {
prefArray = prefArray.filter(pref => pref.name.includes(substring));
}
prefArray.sort((a, b) => a.name > b.name);
if (substring && !gExistingPrefs.has(substring)) {
document.getElementById("prefs").appendChild(createNewPrefFragment(substring));
}
let fragment = createPrefsFragment(prefArray);
document.getElementById("prefs").appendChild(fragment);
}
function createPrefsFragment(prefArray) {
let prefsElement = document.getElementById("prefs");
let fragment = document.createDocumentFragment();
for (let pref of prefArray) {
fragment.appendChild(pref.element);
}
return fragment;
}
function createNewPrefFragment(name) {
let fragment = document.createDocumentFragment();
let row = document.createElement("tr");
row.classList.add("has-user-value");
row.setAttribute("aria-label", name);
let nameCell = document.createElement("td");
nameCell.className = "cell-name";
nameCell.append(name);
row.appendChild(nameCell);
let valueCell = document.createElement("td");
valueCell.classList.add("cell-value");
let guideText = document.createElement("span");
document.l10n.setAttributes(guideText, "about-config-pref-add");
valueCell.appendChild(guideText);
for (let item of ["true", "false", "Number", "String"]) {
let optionBtn = document.createElement("button");
optionBtn.textContent = item;
optionBtn.classList.add("add-" + item);
valueCell.appendChild(optionBtn);
if (substring && !gExistingPrefs.has(substring)) {
fragment.appendChild((new PrefRow(substring)).element);
}
row.appendChild(valueCell);
let editCell = document.createElement("td");
row.appendChild(editCell);
let buttonCell = document.createElement("td");
row.appendChild(buttonCell);
fragment.appendChild(row);
return fragment;
prefsElement.textContent = "";
prefsElement.appendChild(fragment);
}
function prefHasDefaultValue(name) {
@ -321,11 +331,3 @@ function prefHasDefaultValue(name) {
} catch (ex) {}
return false;
}
function addNewPref(name, value) {
Preferences.set(name, value);
let pref = new PrefRow(name);
gExistingPrefs.set(name, pref);
filterPrefs();
return pref;
}

View File

@ -13,7 +13,7 @@ about-config-title = about:config
about-config-search =
.placeholder = Search
about-config-pref-add = Add as:
about-config-pref-add = Add
about-config-pref-toggle = Toggle
about-config-pref-edit = Edit
about-config-pref-save = Save

View File

@ -18,36 +18,74 @@ add_task(async function setup() {
});
add_task(async function test_add_user_pref() {
await AboutConfigTest.withNewTab(async function() {
Assert.ok(!Services.prefs.getChildList("").find(pref => pref == "testPref"));
const PREF_NEW = "test.aboutconfig.new";
Assert.equal(Services.prefs.getPrefType(PREF_NEW),
Ci.nsIPrefBranch.PREF_INVALID);
for (let [buttonSelector, expectedValue] of [
[".add-true", true],
[".add-false", false],
[".add-Number", 0],
[".add-String", ""],
await AboutConfigTest.withNewTab(async function() {
// The row for a new preference appears when searching for its name.
Assert.ok(!this.getRow(PREF_NEW));
this.search(PREF_NEW);
let row = this.getRow(PREF_NEW);
Assert.ok(row.hasClass("deleted"));
for (let [radioIndex, expectedValue, expectedEditingMode] of [
[0, true, false],
[1, 0, true],
[2, "", true],
]) {
this.search("testPref");
this.document.querySelector("#prefs button" + buttonSelector).click();
Assert.ok(Services.prefs.getChildList("").find(pref => pref == "testPref"));
Assert.ok(Preferences.get("testPref") === expectedValue);
this.document.querySelector("#prefs button[data-l10n-id='about-config-pref-delete']").click();
// Adding the preference should set the default for the data type.
row.element.querySelectorAll("input")[radioIndex].click();
row.editColumnButton.click();
Assert.ok(!row.hasClass("deleted"));
Assert.ok(Preferences.get(PREF_NEW) === expectedValue);
// Number and String preferences should be in edit mode.
Assert.equal(!!row.valueInput, expectedEditingMode);
// Repeat the search to verify that the preference remains.
this.search(PREF_NEW);
row = this.getRow(PREF_NEW);
Assert.ok(!row.hasClass("deleted"));
Assert.ok(!row.valueInput);
// Reset the preference, then continue by adding a different type.
row.resetColumnButton.click();
Assert.equal(Services.prefs.getPrefType(PREF_NEW),
Ci.nsIPrefBranch.PREF_INVALID);
}
});
});
add_task(async function test_delete_user_pref() {
Services.prefs.setBoolPref("userAddedPref", true);
await AboutConfigTest.withNewTab(async function() {
let row = this.getRow("userAddedPref");
row.resetColumnButton.click();
Assert.ok(!this.getRow("userAddedPref"));
Assert.ok(!Services.prefs.getChildList("").includes("userAddedPref"));
const PREF_NEW = "test.aboutconfig.new";
// Search for nothing to test gPrefArray
this.search();
Assert.ok(!this.getRow("userAddedPref"));
});
for (let [radioIndex, testValue] of [
[0, false],
[1, -1],
[2, "value"],
]) {
Preferences.set(PREF_NEW, testValue);
await AboutConfigTest.withNewTab(async function() {
// Deleting the preference should keep the row.
let row = this.getRow(PREF_NEW);
row.resetColumnButton.click();
Assert.ok(row.hasClass("deleted"));
Assert.equal(Services.prefs.getPrefType(PREF_NEW),
Ci.nsIPrefBranch.PREF_INVALID);
// Re-adding the preference should keep the same value.
Assert.ok(row.element.querySelectorAll("input")[radioIndex].checked);
row.editColumnButton.click();
Assert.ok(!row.hasClass("deleted"));
Assert.ok(Preferences.get(PREF_NEW) === testValue);
// Searching again after deleting should remove the row.
row.resetColumnButton.click();
this.search();
Assert.ok(!this.getRow(PREF_NEW));
});
}
});
add_task(async function test_reset_user_pref() {
@ -148,11 +186,8 @@ add_task(async function test_modify() {
row.editColumnButton.click();
Assert.equal(row.valueInput.value, Preferences.get(prefName));
row.resetColumnButton.click();
if (willDelete) {
Assert.ok(!this.getRow(prefName));
} else {
Assert.ok(!row.hasClass("has-user-value"));
}
Assert.ok(!row.hasClass("has-user-value"));
Assert.equal(row.hasClass("deleted"), willDelete);
}
});
});

View File

@ -42,8 +42,8 @@ class AboutConfigRowTest {
}
/**
* This is normally "edit" or "toggle" based on the preference type, or "save"
* when the row is in edit mode.
* This is normally "edit" or "toggle" based on the preference type, "save"
* when the row is in edit mode, or "add" when the preference does not exist.
*/
get editColumnButton() {
return this.querySelector("td.cell-edit > button");