Bug 1497727 - Add ability to add prefs on new "about:config" page. r=paolo CLOSED TREE
authorVincent Cote <vincent.cote@uleth.ca>
Wed, 28 Nov 2018 22:09:16 +0000
changeset 448532 f279bb5a7e5a62c3e95f005d221213d603792e13
parent 448531 762547f425d2ef65b36d9e57c29f020cb5361eb4
child 448552 237e92ff9ceb03e634371ab105a69f5f086fc7e0
push id110222
push userpaolo.mozmail@amadzone.org
push dateWed, 28 Nov 2018 22:12:08 +0000
treeherdermozilla-inbound@f279bb5a7e5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaolo
bugs1497727
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 1497727 - Add ability to add prefs on new "about:config" page. r=paolo CLOSED TREE Differential Revision: https://phabricator.services.mozilla.com/D12719
browser/components/aboutconfig/content/aboutconfig.js
browser/components/aboutconfig/content/aboutconfig.notftl
browser/components/aboutconfig/test/browser/browser_edit.js
browser/components/aboutconfig/test/browser/browser_search.js
--- a/browser/components/aboutconfig/content/aboutconfig.js
+++ b/browser/components/aboutconfig/content/aboutconfig.js
@@ -34,17 +34,17 @@ function onLoad() {
       pref.value = "";
     }
     return pref;
   });
 
   gPrefArray.sort((a, b) => a.name > b.name);
 
   document.getElementById("search").addEventListener("keypress", function(e) {
-    if (e.code == "Enter") {
+    if (e.key == "Enter") {
       filterPrefs();
     }
   });
 
   document.getElementById("prefs").addEventListener("click", (event) => {
     if (event.target.localName != "button") {
       return;
     }
@@ -57,16 +57,28 @@ function onLoad() {
       Services.prefs.clearUserPref(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("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 : "");
+      prefRow = [...document.getElementById("prefs").getElementsByTagName("tr")]
+                .find(row => row.querySelector("td").textContent == prefName);
+      startEditingPref(prefRow, gPrefArray.find(p => p.name == prefName));
+      prefRow.querySelector("td.cell-value").firstChild.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) {
@@ -84,23 +96,26 @@ function onLoad() {
       prefRow.querySelector("td.cell-edit").firstChild.focus();
     } else {
       Services.prefs.clearUserPref(prefName);
       gPrefArray.splice(gPrefArray.findIndex(p => p.name == prefName), 1);
       prefRow.remove();
     }
   });
 
-  document.getElementById("prefs").appendChild(createPrefsFragment(gPrefArray));
+  filterPrefs();
 }
 
 function filterPrefs() {
   let substring = document.getElementById("search").value.trim();
+  document.getElementById("prefs").textContent = "";
+  if (substring && !gPrefArray.some(pref => pref.name == substring)) {
+    document.getElementById("prefs").appendChild(createNewPrefFragment(substring));
+  }
   let fragment = createPrefsFragment(gPrefArray.filter(pref => pref.name.includes(substring)));
-  document.getElementById("prefs").textContent = "";
   document.getElementById("prefs").appendChild(fragment);
 }
 
 function createPrefsFragment(prefArray) {
   let fragment = document.createDocumentFragment();
   for (let pref of prefArray) {
     let row = document.createElement("tr");
     if (pref.hasUserValue) {
@@ -109,16 +124,48 @@ function createPrefsFragment(prefArray) 
     row.setAttribute("aria-label", pref.name);
 
     row.appendChild(getPrefRow(pref));
     fragment.appendChild(row);
   }
   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.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);
+  }
+  row.appendChild(valueCell);
+
+  let editCell = document.createElement("td");
+  row.appendChild(editCell);
+
+  let buttonCell = document.createElement("td");
+  row.appendChild(buttonCell);
+
+  fragment.appendChild(row);
+  return fragment;
+}
+
 function getPrefRow(pref) {
   let rowFragment = document.createDocumentFragment();
   let nameCell = document.createElement("td");
   // Add <wbr> behind dots to prevent line breaking in random mid-word places.
   let parts = pref.name.split(".");
   for (let i = 0; i < parts.length - 1; i++) {
     nameCell.append(parts[i] + ".", document.createElement("wbr"));
   }
@@ -238,8 +285,20 @@ function prefHasDefaultValue(name) {
         return true;
       case Ci.nsIPrefBranch.PREF_BOOL:
         gDefaultBranch.getBoolPref(name);
         return true;
     }
   } catch (ex) {}
   return false;
 }
+
+function addNewPref(name, value) {
+  Preferences.set(name, value);
+  gPrefArray.push({
+    name,
+    value,
+    hasUserValue: true,
+    hasDefaultValue: false,
+  });
+  gPrefArray.sort((a, b) => a.name > b.name);
+  filterPrefs();
+}
--- a/browser/components/aboutconfig/content/aboutconfig.notftl
+++ b/browser/components/aboutconfig/content/aboutconfig.notftl
@@ -2,16 +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-add = Add as:
 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
--- a/browser/components/aboutconfig/test/browser/browser_edit.js
+++ b/browser/components/aboutconfig/test/browser/browser_edit.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
+
 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"],
@@ -14,16 +16,52 @@ add_task(async function setup() {
 
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref("accessibility.typeaheadfind.autostart");
     Services.prefs.clearUserPref("accessibility.typeaheadfind.soundURL");
     Services.prefs.clearUserPref("accessibility.typeaheadfind.casesensitive");
   });
 });
 
+add_task(async function test_add_user_pref() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE_URL,
+  }, async browser => {
+    await ContentTask.spawn(browser, null, () => {
+      Assert.ok(!Services.prefs.getChildList("").find(pref => pref == "testPref"));
+      let search = content.document.getElementById("search");
+      search.value = "testPref";
+      search.focus();
+    });
+
+    for (let [buttonSelector, expectedValue] of [
+      [".add-true", true],
+      [".add-false", false],
+      [".add-Number", 0],
+      [".add-String", ""],
+    ]) {
+      EventUtils.sendKey("return");
+      await ContentTask.spawn(browser, [buttonSelector, expectedValue],
+                                      // eslint-disable-next-line no-shadow
+                                      ([buttonSelector, expectedValue]) => {
+        ChromeUtils.import("resource://gre/modules/Preferences.jsm");
+
+        content.document.querySelector("#prefs button" + buttonSelector).click();
+        Assert.ok(Services.prefs.getChildList("").find(pref => pref == "testPref"));
+        Assert.ok(Preferences.get("testPref") === expectedValue);
+        content.document.querySelector("#prefs button[data-l10n-id='about-config-pref-delete']").click();
+        let search = content.document.getElementById("search");
+        search.value = "testPref";
+        search.focus();
+      });
+    }
+  });
+});
+
 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")
--- a/browser/components/aboutconfig/test/browser/browser_search.js
+++ b/browser/components/aboutconfig/test/browser/browser_search.js
@@ -32,18 +32,21 @@ add_task(async function test_search() {
       search.value = "button   ";
       search.focus();
     });
 
     EventUtils.sendKey("return");
     await ContentTask.spawn(browser, prefArray, aPrefArray => {
       let filteredPrefArray =
           aPrefArray.filter(pref => pref.includes("button"));
+      // Adding +1 to the list since button does not match an exact
+      // preference name then a row is added for the user to add a
+      // new button preference if desired
       Assert.equal(content.document.getElementById("prefs").childElementCount,
-                   filteredPrefArray.length);
+                   filteredPrefArray.length + 1);
 
       // Test empty search returns all preferences.
       let search = content.document.getElementById("search");
       search.value = "";
       search.focus();
     });
 
     EventUtils.sendKey("return");
@@ -54,18 +57,19 @@ add_task(async function test_search() {
       // Test invalid search returns no preferences.
       let search = content.document.getElementById("search");
       search.value = "aJunkValueasdf";
       search.focus();
     });
 
     EventUtils.sendKey("return");
     await ContentTask.spawn(browser, prefArray, aPrefArray => {
+      // Expecting 1 row to be returned since it offers the ability to add
       Assert.equal(content.document.getElementById("prefs").childElementCount,
-                   0);
+                   1);
 
       // Test added preferences search returns 2 preferences.
       let search = content.document.getElementById("search");
       search.value = "test.aboutconfig.a";
       search.focus();
     });
 
     EventUtils.sendKey("return");