Bug 1497727 - Add ability to add prefs on new "about:config" page. r=paolo
☠☠ backed out by 762547f425d2 ☠ ☠
authorVincent Cote <vincent.cote@uleth.ca>
Wed, 28 Nov 2018 21:35:30 +0000
changeset 507847 4deb028f32d6794abc15c3663434ab2799f78779
parent 507846 be51be1067583d0e5cacc42f8017b383a70f5f25
child 507848 0ce8f2b44c74bde240c7e66de0198cec83f474d0
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [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 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,51 @@ 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],
+                                      ([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");