Bug 1497726 - Add ability to modify prefs on new "about:config" page. r=paolo
authormatthias <matthias@kammueller.eu>
Wed, 28 Nov 2018 20:40:01 +0000
changeset 505066 be51be1067583d0e5cacc42f8017b383a70f5f25
parent 505065 b67a304cd9ec72707511a8f97d924781639836f2
child 505067 4deb028f32d6794abc15c3663434ab2799f78779
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaolo
bugs1497726
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1497726 - Add ability to modify prefs on new "about:config" page. r=paolo Differential Revision: https://phabricator.services.mozilla.com/D12620
browser/components/aboutconfig/content/aboutconfig.css
browser/components/aboutconfig/content/aboutconfig.js
browser/components/aboutconfig/content/aboutconfig.notftl
browser/components/aboutconfig/test/browser/browser_edit.js
--- a/browser/components/aboutconfig/content/aboutconfig.css
+++ b/browser/components/aboutconfig/content/aboutconfig.css
@@ -42,8 +42,13 @@
 #prefs > tr > td {
   padding: 4px;
   width: 50%;
 }
 
 .cell-value {
   word-break: break-all;
 }
+
+td.cell-value > form > input[type="text"] {
+  width: 100%;
+  box-sizing: border-box;
+}
--- a/browser/components/aboutconfig/content/aboutconfig.js
+++ b/browser/components/aboutconfig/content/aboutconfig.js
@@ -2,16 +2,22 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 
 let gDefaultBranch = Services.prefs.getDefaultBranch("");
 let gPrefArray;
+let gPrefRowInEdit;
+let gPrefInEdit;
+
+function getPrefName(prefRow) {
+  return prefRow.getAttribute("aria-label");
+}
 
 function onLoad() {
   gPrefArray = Services.prefs.getChildList("").map(function(name) {
     let hasUserValue = Services.prefs.prefHasUserValue(name);
     let pref = {
       name,
       value: Preferences.get(name),
       hasUserValue,
@@ -38,31 +44,52 @@ function onLoad() {
     }
   });
 
   document.getElementById("prefs").addEventListener("click", (event) => {
     if (event.target.localName != "button") {
       return;
     }
     let prefRow = event.target.closest("tr");
-    let prefName = prefRow.getAttribute("aria-label");
+    let prefName = getPrefName(prefRow);
+    let pref = gPrefArray.find(p => p.name == prefName);
     let button = event.target.closest("button");
     if (button.classList.contains("button-reset")) {
       // Reset pref and update gPrefArray.
       Services.prefs.clearUserPref(prefName);
-      let pref = gPrefArray.find(p => p.name == prefName);
       pref.value = Preferences.get(prefName);
       pref.hasUserValue = false;
       // Update UI.
       prefRow.textContent = "";
       prefRow.classList.remove("has-user-value");
       prefRow.appendChild(getPrefRow(pref));
+      prefRow.querySelector("td.cell-edit").firstChild.focus();
+    } else if (button.classList.contains("button-toggle")) {
+      // Toggle the pref and update gPrefArray.
+      Services.prefs.setBoolPref(prefName, !pref.value);
+      pref.value = !pref.value;
+      pref.hasUserValue = Services.prefs.prefHasUserValue(pref.name);
+      // Update UI.
+      prefRow.textContent = "";
+      if (pref.hasUserValue) {
+        prefRow.classList.add("has-user-value");
+      } else {
+        prefRow.classList.remove("has-user-value");
+      }
+      prefRow.appendChild(getPrefRow(pref));
+      prefRow.querySelector("td.cell-edit").firstChild.focus();
+    } else if (button.classList.contains("button-edit")) {
+      startEditingPref(prefRow, pref);
+      prefRow.querySelector("td.cell-value").firstChild.firstChild.focus();
+    } else if (button.classList.contains("button-save")) {
+      endEditingPref(prefRow);
+      prefRow.querySelector("td.cell-edit").firstChild.focus();
     } else {
       Services.prefs.clearUserPref(prefName);
-      gPrefArray.splice(gPrefArray.findIndex(pref => pref.name == prefName), 1);
+      gPrefArray.splice(gPrefArray.findIndex(p => p.name == prefName), 1);
       prefRow.remove();
     }
   });
 
   document.getElementById("prefs").appendChild(createPrefsFragment(gPrefArray));
 }
 
 function filterPrefs() {
@@ -98,31 +125,113 @@ function getPrefRow(pref) {
   nameCell.append(parts[parts.length - 1]);
   rowFragment.appendChild(nameCell);
 
   let valueCell = document.createElement("td");
   valueCell.classList.add("cell-value");
   valueCell.textContent = pref.value;
   rowFragment.appendChild(valueCell);
 
+  let editCell = document.createElement("td");
+  editCell.className = "cell-edit";
+  let button = document.createElement("button");
+  if (Services.prefs.getPrefType(pref.name) == Services.prefs.PREF_BOOL) {
+    document.l10n.setAttributes(button, "about-config-pref-toggle");
+    button.className = "button-toggle";
+  } else {
+    document.l10n.setAttributes(button, "about-config-pref-edit");
+    button.className = "button-edit";
+  }
+  editCell.appendChild(button);
+  rowFragment.appendChild(editCell);
+
   let buttonCell = document.createElement("td");
   if (pref.hasUserValue) {
-    let button = document.createElement("button");
+    let resetButton = document.createElement("button");
     if (!pref.hasDefaultValue) {
-      document.l10n.setAttributes(button, "about-config-pref-delete");
+      document.l10n.setAttributes(resetButton, "about-config-pref-delete");
     } else {
-      document.l10n.setAttributes(button, "about-config-pref-reset");
-      button.className = "button-reset";
+      document.l10n.setAttributes(resetButton, "about-config-pref-reset");
+      resetButton.className = "button-reset";
     }
-    buttonCell.appendChild(button);
+    buttonCell.appendChild(resetButton);
   }
   rowFragment.appendChild(buttonCell);
   return rowFragment;
 }
 
+function startEditingPref(row, arrayEntry) {
+  if (gPrefRowInEdit != undefined) {
+    // Abort editing-process first.
+    gPrefRowInEdit.textContent = "";
+    gPrefRowInEdit.appendChild(getPrefRow(gPrefInEdit));
+  }
+  gPrefRowInEdit = row;
+
+  let name = getPrefName(row);
+  gPrefInEdit = arrayEntry;
+
+  let valueCell = row.querySelector("td.cell-value");
+  let oldValue = valueCell.textContent;
+  valueCell.textContent = "";
+  // The form is needed for the invalid-tooltip to appear.
+  let form = document.createElement("form");
+  form.id = "form-" + name;
+  let inputField = document.createElement("input");
+  inputField.type = "text";
+  inputField.value = oldValue;
+  if (Services.prefs.getPrefType(name) == Services.prefs.PREF_INT) {
+    inputField.setAttribute("pattern", "-?[0-9]*");
+    document.l10n.setAttributes(inputField, "about-config-pref-input-number");
+  } else {
+    document.l10n.setAttributes(inputField, "about-config-pref-input-string");
+  }
+  inputField.placeholder = oldValue;
+  form.appendChild(inputField);
+  valueCell.appendChild(form);
+
+  let buttonCell = row.querySelector("td.cell-edit");
+  buttonCell.childNodes[0].remove();
+  let button = document.createElement("button");
+  button.classList.add("primary", "button-save");
+  document.l10n.setAttributes(button, "about-config-pref-save");
+  button.setAttribute("form", form.id);
+  buttonCell.appendChild(button);
+}
+
+function endEditingPref(row) {
+  let name = gPrefInEdit.name;
+  let input = row.querySelector("td.cell-value").firstChild.firstChild;
+  let newValue = input.value;
+
+  if (Services.prefs.getPrefType(name) == Services.prefs.PREF_INT) {
+    let numberValue = parseInt(newValue);
+    if (!/^-?[0-9]*$/.test(newValue) || isNaN(numberValue)) {
+      input.setCustomValidity(input.title);
+      return;
+    }
+    newValue = numberValue;
+    Services.prefs.setIntPref(name, newValue);
+  } else {
+    Services.prefs.setStringPref(name, newValue);
+  }
+
+  // Update gPrefArray.
+  gPrefInEdit.value = newValue;
+  gPrefInEdit.hasUserValue = Services.prefs.prefHasUserValue(name);
+  // Update UI.
+  row.textContent = "";
+  if (gPrefInEdit.hasUserValue) {
+    row.classList.add("has-user-value");
+  } else {
+    row.classList.remove("has-user-value");
+  }
+  row.appendChild(getPrefRow(gPrefInEdit));
+}
+
 function prefHasDefaultValue(name) {
   try {
     switch (Services.prefs.getPrefType(name)) {
       case Ci.nsIPrefBranch.PREF_STRING:
         gDefaultBranch.getStringPref(name);
         return true;
       case Ci.nsIPrefBranch.PREF_INT:
         gDefaultBranch.getIntPref(name);
--- a/browser/components/aboutconfig/content/aboutconfig.notftl
+++ b/browser/components/aboutconfig/content/aboutconfig.notftl
@@ -2,10 +2,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 about-config-title = about:config
 
 about-config-search =
     .placeholder = Search
 
+about-config-pref-toggle = Toggle
+about-config-pref-edit = Edit
+about-config-pref-input-number =
+    .title = Please enter an integer value
+about-config-pref-input-string =
+    .title = Please enter the value
+about-config-pref-save = Save
 about-config-pref-reset = Reset
 about-config-pref-delete = Delete
--- a/browser/components/aboutconfig/test/browser/browser_edit.js
+++ b/browser/components/aboutconfig/test/browser/browser_edit.js
@@ -1,13 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PAGE_URL = "chrome://browser/content/aboutconfig/aboutconfig.html";
 
+add_task(async function setup() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["test.aboutconfig.modify.boolean", true],
+      ["test.aboutconfig.modify.number", 1337],
+      ["test.aboutconfig.modify.string", "the answer to the life the universe and everything"],
+    ],
+  });
+
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("accessibility.typeaheadfind.autostart");
+    Services.prefs.clearUserPref("accessibility.typeaheadfind.soundURL");
+    Services.prefs.clearUserPref("accessibility.typeaheadfind.casesensitive");
+  });
+});
+
 add_task(async function test_delete_user_pref() {
   Services.prefs.setBoolPref("userAddedPref", true);
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: PAGE_URL,
   }, async browser => {
     await ContentTask.spawn(browser, null, () => {
       let list = [...content.document.getElementById("prefs")
@@ -60,17 +76,17 @@ add_task(async function test_reset_user_
       let list = [...content.document.getElementById("prefs")
         .getElementsByTagName("tr")];
       let row = getRow(testPref);
       row.lastChild.lastChild.click();
       // Check new layout and reset.
       list = [...content.document.getElementById("prefs")
         .getElementsByTagName("tr")];
       Assert.ok(!row.classList.contains("has-user-value"));
-      Assert.equal(row.childNodes[2].childNodes.length, 0);
+      Assert.equal(row.lastChild.childNodes.length, 0);
       Assert.ok(!Services.prefs.prefHasUserValue(testPref));
       Assert.equal(getValue(testPref), "" + Preferences.get(testPref));
 
       // Search for nothing to test gPrefArray
       let search = content.document.getElementById("search");
       search.focus();
     });
 
@@ -83,13 +99,91 @@ add_task(async function test_reset_user_
         return getRow(name).querySelector("td.cell-value").textContent;
       }
       let testPref = "browser.autofocus";
       // Check new layout and reset.
       let list = [...content.document.getElementById("prefs")
         .getElementsByTagName("tr")];
       let row = getRow(testPref);
       Assert.ok(!row.classList.contains("has-user-value"));
-      Assert.equal(row.childNodes[2].childNodes.length, 0);
+      Assert.equal(row.lastChild.childNodes.length, 0);
       Assert.equal(getValue(testPref), "" + Preferences.get(testPref));
     });
   });
 });
+
+add_task(async function test_modify() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE_URL,
+  }, browser => {
+    return ContentTask.spawn(browser, null, () => {
+      ChromeUtils.import("resource://gre/modules/Preferences.jsm");
+
+      function getRow(name, list) {
+        return list.find(row => row.querySelector("td").textContent == name);
+      }
+      function getValue(name, list) {
+        return getRow(name, list).querySelector("td.cell-value").textContent;
+      }
+
+      // Test toggle for boolean prefs.
+      let list = [...content.document.getElementById("prefs")
+        .getElementsByTagName("tr")];
+      for (let nameOfBoolPref of [
+        "test.aboutconfig.modify.boolean",
+        "accessibility.typeaheadfind.autostart",
+      ]) {
+        let row = getRow(nameOfBoolPref, list);
+        // Do this a two times to reset the pref.
+        for (let i = 0; i < 2; i++) {
+          row.querySelector("td.cell-edit").firstChild.click();
+          // Check new layout and saving in backend.
+          Assert.equal(getValue(nameOfBoolPref, list),
+            "" + Preferences.get(nameOfBoolPref));
+          let prefHasUserValue = Services.prefs.prefHasUserValue(nameOfBoolPref);
+          Assert.equal(row.classList.contains("has-user-value"), prefHasUserValue);
+          Assert.equal(row.lastChild.childNodes.length > 0, prefHasUserValue);
+        }
+      }
+
+      // Test abort of edit by starting with string and continuing with editing Int pref.
+      let row = getRow("test.aboutconfig.modify.string", list);
+      row.querySelector("td.cell-edit").firstChild.click();
+      row.querySelector("td.cell-value").firstChild.firstChild.value = "test";
+      let intRow = getRow("test.aboutconfig.modify.number", list);
+      intRow.querySelector("td.cell-edit").firstChild.click();
+      Assert.equal(intRow.querySelector("td.cell-value").firstChild.firstChild.value,
+        Preferences.get("test.aboutconfig.modify.number"));
+      Assert.equal(getValue("test.aboutconfig.modify.string", list),
+        "" + Preferences.get("test.aboutconfig.modify.string"));
+      Assert.equal(row.querySelector("td.cell-value").textContent,
+        Preferences.get("test.aboutconfig.modify.string"));
+
+      // Test regex check for Int pref.
+      intRow.querySelector("td.cell-value").firstChild.firstChild.value += "a";
+      intRow.querySelector("td.cell-edit").firstChild.click();
+      Assert.ok(!intRow.querySelector("td.cell-value").firstChild.firstChild.checkValidity());
+
+      // Test correct saving and DOM-update.
+      for (let prefName of [
+        "test.aboutconfig.modify.string",
+        "test.aboutconfig.modify.number",
+        "accessibility.typeaheadfind.soundURL",
+        "accessibility.typeaheadfind.casesensitive",
+      ]) {
+        row = getRow(prefName, list);
+        // Activate edit and check displaying.
+        row.querySelector("td.cell-edit").firstChild.click();
+        Assert.equal(row.querySelector("td.cell-value").firstChild.firstChild.value,
+          Preferences.get(prefName));
+        row.querySelector("td.cell-value").firstChild.firstChild.value = "42";
+        // Save and check saving.
+        row.querySelector("td.cell-edit").firstChild.click();
+        Assert.equal(getValue(prefName, list),
+          "" + Preferences.get(prefName));
+        let prefHasUserValue = Services.prefs.prefHasUserValue(prefName);
+        Assert.equal(row.lastChild.childNodes.length > 0, prefHasUserValue);
+        Assert.equal(row.classList.contains("has-user-value"), prefHasUserValue);
+      }
+    });
+  });
+});