mobile/android/chrome/content/config.js

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     4 "use strict";
     6 const {classes: Cc, interfaces: Ci, manager: Cm, utils: Cu} = Components;
     7 Cu.import("resource://gre/modules/Services.jsm");
     9 const VKB_ENTER_KEY = 13;   // User press of VKB enter key
    10 const INITIAL_PAGE_DELAY = 500;   // Initial pause on program start for scroll alignment
    11 const PREFS_BUFFER_MAX = 30;   // Max prefs buffer size for getPrefsBuffer()
    12 const PAGE_SCROLL_TRIGGER = 200;     // Triggers additional getPrefsBuffer() on user scroll-to-bottom
    13 const FILTER_CHANGE_TRIGGER = 200;     // Delay between responses to filterInput changes
    14 const INNERHTML_VALUE_DELAY = 100;    // Delay before providing prefs innerHTML value
    16 let gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties");
    17 let gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
    20 /* ============================== NewPrefDialog ==============================
    21  *
    22  * New Preference Dialog Object and methods
    23  *
    24  * Implements User Interfaces for creation of a single(new) Preference setting
    25  *
    26  */
    27 var NewPrefDialog = {
    29   _prefsShield: null,
    31   _newPrefsDialog: null,
    32   _newPrefItem: null,
    33   _prefNameInputElt: null,
    34   _prefTypeSelectElt: null,
    36   _booleanValue: null,
    37   _booleanToggle: null,
    38   _stringValue: null,
    39   _intValue: null,
    41   _positiveButton: null,
    43   get type() {
    44     return this._prefTypeSelectElt.value;
    45   },
    47   set type(aType) {
    48     this._prefTypeSelectElt.value = aType;
    49     switch(this._prefTypeSelectElt.value) {
    50       case "boolean":
    51         this._prefTypeSelectElt.selectedIndex = 0;
    52         break;
    53       case "string":
    54         this._prefTypeSelectElt.selectedIndex = 1;
    55         break;
    56       case "int":
    57         this._prefTypeSelectElt.selectedIndex = 2;
    58         break;
    59     }
    61     this._newPrefItem.setAttribute("typestyle", aType);
    62   },
    64   // Init the NewPrefDialog
    65   init: function AC_init() {
    66     this._prefsShield = document.getElementById("prefs-shield");
    68     this._newPrefsDialog = document.getElementById("new-pref-container");
    69     this._newPrefItem = document.getElementById("new-pref-item");
    70     this._prefNameInputElt = document.getElementById("new-pref-name");
    71     this._prefTypeSelectElt = document.getElementById("new-pref-type");
    73     this._booleanValue = document.getElementById("new-pref-value-boolean");
    74     this._stringValue = document.getElementById("new-pref-value-string");
    75     this._intValue = document.getElementById("new-pref-value-int");
    77     this._positiveButton = document.getElementById("positive-button");
    78   },
    80   // Called to update positive button to display text ("Create"/"Change), and enabled/disabled status
    81   // As new pref name is initially displayed, re-focused, or modifed during user input
    82   _updatePositiveButton: function AC_updatePositiveButton(aPrefName) {
    83     this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton");
    84     this._positiveButton.setAttribute("disabled", true);
    85     if (aPrefName == "") {
    86       return;
    87     }
    89     // If item already in list, it's being changed, else added
    90     let item = document.querySelector(".pref-item[name=" + aPrefName.quote() + "]");
    91     if (item) {
    92       this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton");
    93     } else {
    94       this._positiveButton.removeAttribute("disabled");
    95     }
    96   },
    98   // When we want to cancel/hide an existing, or show a new pref dialog
    99   toggleShowHide: function AC_toggleShowHide() {
   100     if (this._newPrefsDialog.classList.contains("show")) {
   101       this.hide();
   102     } else {
   103       this._show();
   104     }
   105   },
   107   // When we want to show the new pref dialog / shield the prefs list
   108   _show: function AC_show() {
   109     this._newPrefsDialog.classList.add("show");
   110     this._prefsShield.setAttribute("shown", true);
   112     // Initial default field values
   113     this._prefNameInputElt.value = "";
   114     this._updatePositiveButton(this._prefNameInputElt.value);
   116     this.type = "boolean";
   117     this._booleanValue.value = "false";
   118     this._stringValue.value = "";
   119     this._intValue.value = "";
   121     this._prefNameInputElt.focus();
   123     window.addEventListener("keypress", this.handleKeypress, false);
   124   },
   126   // When we want to cancel/hide the new pref dialog / un-shield the prefs list
   127   hide: function AC_hide() {
   128     this._newPrefsDialog.classList.remove("show");
   129     this._prefsShield.removeAttribute("shown");
   131     window.removeEventListener("keypress", this.handleKeypress, false);
   132   },
   134   // Watch user key input so we can provide Enter key action, commit input values
   135   handleKeypress: function AC_handleKeypress(aEvent) {
   136     // Close our VKB on new pref enter key press
   137     if (aEvent.keyCode == VKB_ENTER_KEY)
   138       aEvent.target.blur();
   139   },
   141   // New prefs create dialog only allows creating a non-existing preference, doesn't allow for
   142   // Changing an existing one on-the-fly, tap existing/displayed line item pref for that
   143   create: function AC_create(aEvent) {
   144     if (this._positiveButton.getAttribute("disabled") == "true") {
   145       return;
   146     }
   148     switch(this.type) {
   149       case "boolean":
   150         Services.prefs.setBoolPref(this._prefNameInputElt.value, (this._booleanValue.value == "true") ? true : false);
   151         break;
   152       case "string":
   153         Services.prefs.setCharPref(this._prefNameInputElt.value, this._stringValue.value);
   154         break;
   155       case "int":
   156         Services.prefs.setIntPref(this._prefNameInputElt.value, this._intValue.value);
   157         break;
   158     }
   160     this.hide();
   161   },
   163   // Display proper positive button text/state on new prefs name input focus
   164   focusName: function AC_focusName(aEvent) {
   165     this._updatePositiveButton(aEvent.target.value);
   166   },
   168   // Display proper positive button text/state as user changes new prefs name
   169   updateName: function AC_updateName(aEvent) {
   170     this._updatePositiveButton(aEvent.target.value);
   171   },
   173   // In new prefs dialog, bool prefs are <input type="text">, as they aren't yet tied to an
   174   // Actual Services.prefs.*etBoolPref()
   175   toggleBoolValue: function AC_toggleBoolValue() {
   176     this._booleanValue.value = (this._booleanValue.value == "true" ? "false" : "true");
   177   }
   178 }
   181 /* ============================== AboutConfig ==============================
   182  *
   183  * Main AboutConfig object and methods
   184  *
   185  * Implements User Interfaces for maintenance of a list of Preference settings
   186  *
   187  */
   188 var AboutConfig = {
   190   contextMenuLINode: null,
   191   filterInput: null,
   192   _filterPrevInput: null,
   193   _filterChangeTimer: null,
   194   _prefsContainer: null,
   195   _loadingContainer: null,
   196   _list: null,
   198   // Init the main AboutConfig dialog
   199   init: function AC_init() {
   200     this.filterInput = document.getElementById("filter-input");
   201     this._prefsContainer = document.getElementById("prefs-container");
   202     this._loadingContainer = document.getElementById("loading-container");
   204     let list = Services.prefs.getChildList("");
   205     this._list = list.sort().map( function AC_getMapPref(aPref) {
   206       return new Pref(aPref);
   207     }, this);
   209     // Display the current prefs list (retains searchFilter value)
   210     this.bufferFilterInput();
   212     // Setup the prefs observers
   213     Services.prefs.addObserver("", this, false);
   214   },
   216   // Uninit the main AboutConfig dialog
   217   uninit: function AC_uninit() {
   218     // Remove the prefs observer
   219     Services.prefs.removeObserver("", this);
   221     // Ensure pref adds/changes/resets flushed to disk on unload
   222     Services.prefs.savePrefFile(null);
   223   },
   225   // Clear the filterInput value, to display the entire list
   226   clearFilterInput: function AC_clearFilterInput() {
   227     this.filterInput.value = "";
   228     this.bufferFilterInput();
   229   },
   231   // Buffer down rapid changes in filterInput value from keyboard
   232   bufferFilterInput: function AC_bufferFilterInput() {
   233     if (this._filterChangeTimer) {
   234       clearTimeout(this._filterChangeTimer);
   235     }
   237     this._filterChangeTimer = setTimeout((function() {
   238       this._filterChangeTimer = null;
   239       // Display updated prefs list when filterInput value settles
   240       this._displayNewList();
   241     }).bind(this), FILTER_CHANGE_TRIGGER);
   242   },
   244   // Update displayed list when filterInput value changes
   245   _displayNewList: function AC_displayNewList() {
   246     // This survives the search filter value past a page refresh
   247     this.filterInput.setAttribute("value", this.filterInput.value);
   249     // Don't start new filter search if same as last
   250     if (this.filterInput.value == this._filterPrevInput) {
   251       return;
   252     }
   253     this._filterPrevInput = this.filterInput.value;
   255     // Clear list item selection / context menu, prefs list, get first buffer, set scrolling on
   256     this.selected = "";
   257     this._clearPrefsContainer();
   258     this._addMorePrefsToContainer();
   259     window.onscroll = this.onScroll.bind(this);
   261     // Pause for screen to settle, then ensure at top
   262     setTimeout((function() {
   263       window.scrollTo(0, 0);
   264     }).bind(this), INITIAL_PAGE_DELAY);
   265   },
   267   // Clear the displayed preferences list
   268   _clearPrefsContainer: function AC_clearPrefsContainer() {
   269     // Quick clear the prefsContainer list
   270     let empty = this._prefsContainer.cloneNode(false);
   271     this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer); 
   272     this._prefsContainer = empty;
   274     // Quick clear the prefs li.HTML list
   275     this._list.forEach(function(item) {
   276       delete item.li;
   277     });
   278   },
   280   // Get a small manageable block of prefs items, and add them to the displayed list
   281   _addMorePrefsToContainer: function AC_addMorePrefsToContainer() {
   282     // Create filter regex
   283     let filterExp = this.filterInput.value ?
   284       new RegExp(this.filterInput.value, "i") : null;
   286     // Get a new block for the display list
   287     let prefsBuffer = [];
   288     for (let i = 0; i < this._list.length && prefsBuffer.length < PREFS_BUFFER_MAX; i++) {
   289       if (!this._list[i].li && this._list[i].test(filterExp)) {
   290         prefsBuffer.push(this._list[i]);
   291       }
   292     }
   294     // Add the new block to the displayed list
   295     for (let i = 0; i < prefsBuffer.length; i++) {
   296       this._prefsContainer.appendChild(prefsBuffer[i].getOrCreateNewLINode());
   297     }
   299     // Determine if anything left to add later by scrolling
   300     let anotherPrefsBufferRemains = false;
   301     for (let i = 0; i < this._list.length; i++) {
   302       if (!this._list[i].li && this._list[i].test(filterExp)) {
   303         anotherPrefsBufferRemains = true;
   304         break;
   305       }
   306     }
   308     if (anotherPrefsBufferRemains) {
   309       // If still more could be displayed, show the throbber
   310       this._loadingContainer.style.display = "block";
   311     } else {
   312       // If no more could be displayed, hide the throbber, and stop noticing scroll events
   313       this._loadingContainer.style.display = "none";
   314       window.onscroll = null;
   315     }
   316   },
   318   // If scrolling at the bottom, maybe add some more entries
   319   onScroll: function AC_onScroll(aEvent) {
   320     if (this._prefsContainer.scrollHeight - (window.pageYOffset + window.innerHeight) < PAGE_SCROLL_TRIGGER) {
   321       if (!this._filterChangeTimer) {
   322         this._addMorePrefsToContainer();
   323       }
   324     }
   325   },
   328   // Return currently selected list item node
   329   get selected() {
   330     return document.querySelector(".pref-item.selected");
   331   },
   333   // Set list item node as selected
   334   set selected(aSelection) {
   335     let currentSelection = this.selected;
   336     if (aSelection == currentSelection) {
   337       return;
   338     }
   340     // Clear any previous selection
   341     if (currentSelection) {
   342       currentSelection.classList.remove("selected");
   343       currentSelection.removeEventListener("keypress", this.handleKeypress, false);
   344     }
   346     // Set any current selection
   347     if (aSelection) {
   348       aSelection.classList.add("selected");
   349       aSelection.addEventListener("keypress", this.handleKeypress, false);
   350     }
   351   },
   353   // Watch user key input so we can provide Enter key action, commit input values
   354   handleKeypress: function AC_handleKeypress(aEvent) {
   355     if (aEvent.keyCode == VKB_ENTER_KEY)
   356       aEvent.target.blur();
   357   },
   359   // Return the target list item node of an action event
   360   getLINodeForEvent: function AC_getLINodeForEvent(aEvent) {
   361     let node = aEvent.target;
   362     while (node && node.nodeName != "li") {
   363       node = node.parentNode;
   364     }
   366     return node;
   367   },
   369   // Return a pref of a list item node
   370   _getPrefForNode: function AC_getPrefForNode(aNode) {
   371     let pref = aNode.getAttribute("name");
   373     return new Pref(pref);
   374   },
   376   // When list item name or value are tapped
   377   selectOrToggleBoolPref: function AC_selectOrToggleBoolPref(aEvent) {
   378     let node = this.getLINodeForEvent(aEvent);
   380     // If not already selected, just do so
   381     if (this.selected != node) {
   382       this.selected = node;
   383       return;
   384     }
   386     // If already selected, and value is boolean, toggle it
   387     let pref = this._getPrefForNode(node);
   388     if (pref.type != Services.prefs.PREF_BOOL) {
   389       return;
   390     }
   392     this.toggleBoolPref(aEvent);
   393   },
   395   // When finalizing list input values due to blur
   396   setIntOrStringPref: function AC_setIntOrStringPref(aEvent) {
   397     let node = this.getLINodeForEvent(aEvent);
   399     // Skip if locked
   400     let pref = this._getPrefForNode(node);
   401     if (pref.locked) {
   402       return;
   403     }
   405     // Boolean inputs blur to remove focus from "button"
   406     if (pref.type == Services.prefs.PREF_BOOL) {
   407       return;
   408     }
   410     // String and Int inputs change / commit on blur
   411     pref.value = aEvent.target.value;
   412   },
   414   // When we reset a pref to it's default value (note resetting a user created pref will delete it)
   415   resetDefaultPref: function AC_resetDefaultPref(aEvent) {
   416     let node = this.getLINodeForEvent(aEvent);
   418     // If not already selected, do so
   419     if (this.selected != node) {
   420       this.selected = node;
   421     }
   423     // Reset will handle any locked condition
   424     let pref = this._getPrefForNode(node);
   425     pref.reset();
   426   },
   428   // When we want to toggle a bool pref
   429   toggleBoolPref: function AC_toggleBoolPref(aEvent) {
   430     let node = this.getLINodeForEvent(aEvent);
   432     // Skip if locked, or not boolean
   433     let pref = this._getPrefForNode(node);
   434     if (pref.locked) {
   435       return;
   436     }
   438     // Toggle, and blur to remove field focus
   439     pref.value = !pref.value;
   440     aEvent.target.blur();
   441   },
   443   // When Int inputs have their Up or Down arrows toggled
   444   incrOrDecrIntPref: function AC_incrOrDecrIntPref(aEvent, aInt) {
   445     let node = this.getLINodeForEvent(aEvent);
   447     // Skip if locked
   448     let pref = this._getPrefForNode(node);
   449     if (pref.locked) {
   450       return;
   451     }
   453     pref.value += aInt;
   454   },
   456   // Observe preference changes
   457   observe: function AC_observe(aSubject, aTopic, aPrefName) {
   458     let pref = new Pref(aPrefName);
   460     // Ignore uninteresting changes, and avoid "private" preferences
   461     if (aTopic != "nsPref:changed") {
   462       return;
   463     }
   465     // If pref type invalid, refresh display as user reset/removed an item from the list
   466     if (pref.type == Services.prefs.PREF_INVALID) {
   467       document.location.reload();
   468       return;
   469     }
   471     // If pref not already in list, refresh display as it's being added
   472     let item = document.querySelector(".pref-item[name=" + pref.name.quote() + "]");
   473     if (!item) {
   474       document.location.reload();
   475       return;
   476     }
   478     // Else we're modifying a pref
   479     item.setAttribute("value", pref.value);
   480     let input = item.querySelector("input");
   481     input.setAttribute("value", pref.value);
   482     input.value = pref.value;
   484     pref.default ?
   485       item.querySelector(".reset").setAttribute("disabled", "true") :
   486       item.querySelector(".reset").removeAttribute("disabled");
   487   },
   489   // Quick context menu helpers for about:config
   490   clipboardCopy: function AC_clipboardCopy(aField) {
   491     let pref = this._getPrefForNode(this.contextMenuLINode);
   492     if (aField == 'name') {
   493       gClipboardHelper.copyString(pref.name);
   494     } else {
   495       gClipboardHelper.copyString(pref.value);
   496     }
   497   }
   498 }
   501 /* ============================== Pref ==============================
   502  *
   503  * Individual Preference object / methods
   504  *
   505  * Defines a Pref object, a document list item tied to Preferences Services
   506  * And the methods by which they interact.
   507  *
   508  */
   509 function Pref(aName) {
   510   this.name = aName;
   511 }
   513 Pref.prototype = {
   514   get type() {
   515     return Services.prefs.getPrefType(this.name);
   516   },
   518   get value() {
   519     switch (this.type) {
   520       case Services.prefs.PREF_BOOL:
   521         return Services.prefs.getBoolPref(this.name);
   522       case Services.prefs.PREF_INT:
   523         return Services.prefs.getIntPref(this.name);
   524       case Services.prefs.PREF_STRING:
   525       default:
   526         return Services.prefs.getCharPref(this.name);
   527     }
   529   },
   530   set value(aPrefValue) {
   531     switch (this.type) {
   532       case Services.prefs.PREF_BOOL:
   533         Services.prefs.setBoolPref(this.name, aPrefValue);
   534         break;
   535       case Services.prefs.PREF_INT:
   536         Services.prefs.setIntPref(this.name, aPrefValue);
   537         break;
   538       case Services.prefs.PREF_STRING:
   539       default:
   540         Services.prefs.setCharPref(this.name, aPrefValue);
   541     }
   542   },
   544   get default() {
   545     return !Services.prefs.prefHasUserValue(this.name);
   546   },
   548   get locked() {
   549     return Services.prefs.prefIsLocked(this.name);
   550   },
   552   reset: function AC_reset() {
   553     Services.prefs.clearUserPref(this.name);
   554   },
   556   test: function AC_test(aValue) {
   557     return aValue ? aValue.test(this.name) : true;
   558   },
   560   // Get existing or create new LI node for the pref
   561   getOrCreateNewLINode: function AC_getOrCreateNewLINode() {
   562     if (!this.li) {
   563       this.li = document.createElement("li");
   565       this.li.className = "pref-item";
   566       this.li.setAttribute("name", this.name);
   568       // Click callback to ensure list item selected even on no-action tap events
   569       this.li.addEventListener("click",
   570         function(aEvent) {
   571           AboutConfig.selected = AboutConfig.getLINodeForEvent(aEvent);
   572         },
   573         false
   574       );
   576       // Contextmenu callback to identify selected list item
   577       this.li.addEventListener("contextmenu",
   578         function(aEvent) {
   579           AboutConfig.contextMenuLINode = AboutConfig.getLINodeForEvent(aEvent);
   580         },
   581         false
   582       );
   584       this.li.setAttribute("contextmenu", "prefs-context-menu");
   586       // Create list item outline, bind to object actions
   587       this.li.innerHTML =
   588         "<div class='pref-name' " +
   589             "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" +
   590             this.name +
   591         "</div>" +
   592         "<div class='pref-item-line'>" +
   593           "<input class='pref-value' value='' " +
   594             "onblur='AboutConfig.setIntOrStringPref(event);' " +
   595             "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" +
   596           "</input>" +
   597           "<div class='pref-button reset' " +
   598             "onclick='AboutConfig.resetDefaultPref(event);'>" +
   599             gStringBundle.GetStringFromName("pref.resetButton") +
   600           "</div>" +
   601           "<div class='pref-button toggle' " +
   602             "onclick='AboutConfig.toggleBoolPref(event);'>" +
   603             gStringBundle.GetStringFromName("pref.toggleButton") +
   604           "</div>" +
   605           "<div class='pref-button up' " +
   606             "onclick='AboutConfig.incrOrDecrIntPref(event, 1);'>" +
   607           "</div>" +
   608           "<div class='pref-button down' " +
   609             "onclick='AboutConfig.incrOrDecrIntPref(event, -1);'>" +
   610           "</div>" +
   611         "</div>";
   613       // Delay providing the list item values, until the LI is returned and added to the document
   614       setTimeout(this._valueSetup.bind(this), INNERHTML_VALUE_DELAY);
   615     }
   617     return this.li;
   618   },
   620   // Initialize list item object values
   621   _valueSetup: function AC_valueSetup() {
   623     this.li.setAttribute("type", this.type);
   624     this.li.setAttribute("value", this.value);
   626     let valDiv = this.li.querySelector(".pref-value");
   627     valDiv.value = this.value;
   629     switch(this.type) {
   630       case Services.prefs.PREF_BOOL:
   631         valDiv.setAttribute("type", "button");
   632         this.li.querySelector(".up").setAttribute("disabled", true);
   633         this.li.querySelector(".down").setAttribute("disabled", true);
   634         break;
   635       case Services.prefs.PREF_STRING:
   636         valDiv.setAttribute("type", "text");
   637         this.li.querySelector(".up").setAttribute("disabled", true);
   638         this.li.querySelector(".down").setAttribute("disabled", true);
   639         this.li.querySelector(".toggle").setAttribute("disabled", true);
   640         break;
   641       case Services.prefs.PREF_INT:
   642         valDiv.setAttribute("type", "number");
   643         this.li.querySelector(".toggle").setAttribute("disabled", true);
   644         break;
   645     }
   647     this.li.setAttribute("default", this.default);
   648     if (this.default) {
   649       this.li.querySelector(".reset").setAttribute("disabled", true);
   650     }
   652     if (this.locked) {
   653       valDiv.setAttribute("disabled", this.locked);
   654       this.li.querySelector(".pref-name").setAttribute("locked", true);
   655     }
   656   }
   657 }

mercurial