browser/metro/base/content/pages/config.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     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 INITIAL_PAGE_DELAY = 500;   // Initial pause on program start for scroll alignment
    10 const PREFS_BUFFER_MAX = 100;   // Max prefs buffer size for getPrefsBuffer()
    11 const PAGE_SCROLL_TRIGGER = 200;     // Triggers additional getPrefsBuffer() on user scroll-to-bottom
    12 const FILTER_CHANGE_TRIGGER = 200;     // Delay between responses to filterInput changes
    13 const INNERHTML_VALUE_DELAY = 100;    // Delay before providing prefs innerHTML value
    15 let gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties");
    16 let gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
    19 /* ============================== NewPrefDialog ==============================
    20  *
    21  * New Preference Dialog Object and methods
    22  *
    23  * Implements User Interfaces for creation of a single(new) Preference setting
    24  *
    25  */
    26 var NewPrefDialog = {
    28   _prefsShield: null,
    30   _newPrefsDialog: null,
    31   _newPrefItem: null,
    32   _prefNameInputElt: null,
    33   _prefTypeSelectElt: null,
    35   _booleanValue: null,
    36   _booleanToggle: null,
    37   _stringValue: null,
    38   _intValue: null,
    40   _positiveButton: null,
    42   get type() {
    43     return this._prefTypeSelectElt.value;
    44   },
    46   set type(aType) {
    47     this._prefTypeSelectElt.value = aType;
    48     switch(this._prefTypeSelectElt.value) {
    49       case "boolean":
    50         this._prefTypeSelectElt.selectedIndex = 0;
    51         break;
    52       case "string":
    53         this._prefTypeSelectElt.selectedIndex = 1;
    54         break;
    55       case "int":
    56         this._prefTypeSelectElt.selectedIndex = 2;
    57         break;
    58     }
    60     this._newPrefItem.setAttribute("typestyle", aType);
    61   },
    63   // Init the NewPrefDialog
    64   init: function AC_init() {
    65     this._prefsShield = document.getElementById("prefs-shield");
    67     this._newPrefsDialog = document.getElementById("new-pref-container");
    68     this._newPrefItem = document.getElementById("new-pref-item");
    69     this._prefNameInputElt = document.getElementById("new-pref-name");
    70     this._prefTypeSelectElt = document.getElementById("new-pref-type");
    72     this._booleanValue = document.getElementById("new-pref-value-boolean");
    73     this._stringValue = document.getElementById("new-pref-value-string");
    74     this._intValue = document.getElementById("new-pref-value-int");
    76     this._positiveButton = document.getElementById("positive-button");
    77   },
    79   // Called to update positive button to display text ("Create"/"Change), and enabled/disabled status
    80   // As new pref name is initially displayed, re-focused, or modifed during user input
    81   _updatePositiveButton: function AC_updatePositiveButton(aPrefName) {
    82     this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton");
    83     this._positiveButton.setAttribute("disabled", true);
    84     if (aPrefName == "") {
    85       return;
    86     }
    88     // If item already in list, it's being changed, else added
    89     let item = document.querySelector(".pref-item[name=" + aPrefName.quote() + "]");
    90     if (item) {
    91       this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton");
    92     } else {
    93       this._positiveButton.removeAttribute("disabled");
    94     }
    95   },
    97   // When we want to cancel/hide an existing, or show a new pref dialog
    98   toggleShowHide: function AC_toggleShowHide() {
    99     if (this._newPrefsDialog.classList.contains("show")) {
   100       this.hide();
   101     } else {
   102       this._show();
   103     }
   104   },
   106   // When we want to show the new pref dialog / shield the prefs list
   107   _show: function AC_show() {
   108     this._newPrefsDialog.classList.add("show");
   109     this._prefsShield.setAttribute("shown", true);
   111     // Initial default field values
   112     this._prefNameInputElt.value = "";
   113     this._updatePositiveButton(this._prefNameInputElt.value);
   115     this.type = "boolean";
   116     this._booleanValue.value = "false";
   117     this._stringValue.value = "";
   118     this._intValue.value = "";
   120     this._prefNameInputElt.focus();
   122     window.addEventListener("keypress", this.handleKeypress, false);
   123   },
   125   // When we want to cancel/hide the new pref dialog / un-shield the prefs list
   126   hide: function AC_hide() {
   127     this._newPrefsDialog.classList.remove("show");
   128     this._prefsShield.removeAttribute("shown");
   130     window.removeEventListener("keypress", this.handleKeypress, false);
   131   },
   133   // Watch user key input so we can provide Enter key action, commit input values
   134   handleKeypress: function AC_handleKeypress(aEvent) {
   135     // Close our VKB on new pref enter key press
   136     if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
   137       aEvent.target.blur();
   138   },
   140   // New prefs create dialog only allows creating a non-existing preference, doesn't allow for
   141   // Changing an existing one on-the-fly, tap existing/displayed line item pref for that
   142   create: function AC_create(aEvent) {
   143     if (this._positiveButton.getAttribute("disabled") == "true") {
   144       return;
   145     }
   147     switch(this.type) {
   148       case "boolean":
   149         Services.prefs.setBoolPref(this._prefNameInputElt.value, (this._booleanValue.value == "true") ? true : false);
   150         break;
   151       case "string":
   152         Services.prefs.setCharPref(this._prefNameInputElt.value, this._stringValue.value);
   153         break;
   154       case "int":
   155         Services.prefs.setIntPref(this._prefNameInputElt.value, this._intValue.value);
   156         break;
   157     }
   159     this.hide();
   160   },
   162   // Display proper positive button text/state on new prefs name input focus
   163   focusName: function AC_focusName(aEvent) {
   164     this._updatePositiveButton(aEvent.target.value);
   165   },
   167   // Display proper positive button text/state as user changes new prefs name
   168   updateName: function AC_updateName(aEvent) {
   169     this._updatePositiveButton(aEvent.target.value);
   170   },
   172   // In new prefs dialog, bool prefs are <input type="text">, as they aren't yet tied to an
   173   // Actual Services.prefs.*etBoolPref()
   174   toggleBoolValue: function AC_toggleBoolValue() {
   175     this._booleanValue.value = (this._booleanValue.value == "true" ? "false" : "true");
   176   }
   177 }
   180 /* ============================== AboutConfig ==============================
   181  *
   182  * Main AboutConfig object and methods
   183  *
   184  * Implements User Interfaces for maintenance of a list of Preference settings
   185  *
   186  */
   187 var AboutConfig = {
   189   filterInput: null,
   190   _filterPrevInput: null,
   191   _filterChangeTimer: null,
   192   _prefsContainer: null,
   193   _loadingContainer: null,
   194   _list: null,
   196   // Init the main AboutConfig dialog
   197   init: function AC_init() {
   198     this.filterInput = document.getElementById("filter-input");
   199     this._prefsContainer = document.getElementById("prefs-container");
   200     this._loadingContainer = document.getElementById("loading-container");
   202     let list = Services.prefs.getChildList("");
   203     this._list = list.sort().map( function AC_getMapPref(aPref) {
   204       return new Pref(aPref);
   205     }, this);
   207     // Display the current prefs list (retains searchFilter value)
   208     this.bufferFilterInput();
   210     // Setup the prefs observers
   211     Services.prefs.addObserver("", this, false);
   212   },
   214   // Uninit the main AboutConfig dialog
   215   uninit: function AC_uninit() {
   216     // Remove the prefs observer
   217     Services.prefs.removeObserver("", this);
   218   },
   220   // Clear the filterInput value, to display the entire list
   221   clearFilterInput: function AC_clearFilterInput() {
   222     this.filterInput.value = "";
   223     this.bufferFilterInput();
   224   },
   226   // Buffer down rapid changes in filterInput value from keyboard
   227   bufferFilterInput: function AC_bufferFilterInput() {
   228     if (this._filterChangeTimer) {
   229       clearTimeout(this._filterChangeTimer);
   230     }
   232     this._filterChangeTimer = setTimeout((function() {
   233       this._filterChangeTimer = null;
   234       // Display updated prefs list when filterInput value settles
   235       this._displayNewList();
   236     }).bind(this), FILTER_CHANGE_TRIGGER);
   237   },
   239   // Update displayed list when filterInput value changes
   240   _displayNewList: function AC_displayNewList() {
   241     // This survives the search filter value past a page refresh
   242     this.filterInput.setAttribute("value", this.filterInput.value);
   244     // Don't start new filter search if same as last
   245     if (this.filterInput.value == this._filterPrevInput) {
   246       return;
   247     }
   248     this._filterPrevInput = this.filterInput.value;
   250     // Clear list item selection and prefs list, get first buffer, set scrolling on
   251     this.selected = "";
   252     this._clearPrefsContainer();
   253     this._addMorePrefsToContainer();
   254     window.onscroll = this.onScroll.bind(this);
   256     // Pause for screen to settle, then ensure at top
   257     setTimeout((function() {
   258       window.scrollTo(0, 0);
   259     }).bind(this), INITIAL_PAGE_DELAY);
   260   },
   262   // Clear the displayed preferences list
   263   _clearPrefsContainer: function AC_clearPrefsContainer() {
   264     // Quick clear the prefsContainer list
   265     let empty = this._prefsContainer.cloneNode(false);
   266     this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer);
   267     this._prefsContainer = empty;
   269     // Quick clear the prefs li.HTML list
   270     this._list.forEach(function(item) {
   271       delete item.li;
   272     });
   273   },
   275   // Get a small manageable block of prefs items, and add them to the displayed list
   276   _addMorePrefsToContainer: function AC_addMorePrefsToContainer() {
   277     // Create filter regex
   278     let filterExp = this.filterInput.value ?
   279       new RegExp(this.filterInput.value, "i") : null;
   281     // Get a new block for the display list
   282     let prefsBuffer = [];
   283     for (let i = 0; i < this._list.length && prefsBuffer.length < PREFS_BUFFER_MAX; i++) {
   284       if (!this._list[i].li && this._list[i].test(filterExp)) {
   285         prefsBuffer.push(this._list[i]);
   286       }
   287     }
   289     // Add the new block to the displayed list
   290     for (let i = 0; i < prefsBuffer.length; i++) {
   291       this._prefsContainer.appendChild(prefsBuffer[i].getOrCreateNewLINode());
   292     }
   294     // Determine if anything left to add later by scrolling
   295     let anotherPrefsBufferRemains = false;
   296     for (let i = 0; i < this._list.length; i++) {
   297       if (!this._list[i].li && this._list[i].test(filterExp)) {
   298         anotherPrefsBufferRemains = true;
   299         break;
   300       }
   301     }
   303     if (anotherPrefsBufferRemains) {
   304       // If still more could be displayed, show the throbber
   305       this._loadingContainer.style.display = "block";
   306     } else {
   307       // If no more could be displayed, hide the throbber, and stop noticing scroll events
   308       this._loadingContainer.style.display = "none";
   309       window.onscroll = null;
   310     }
   311   },
   313   // If scrolling at the bottom, maybe add some more entries
   314   onScroll: function AC_onScroll(aEvent) {
   315     if (this._prefsContainer.scrollHeight - (window.pageYOffset + window.innerHeight) < PAGE_SCROLL_TRIGGER) {
   316       if (!this._filterChangeTimer) {
   317         this._addMorePrefsToContainer();
   318       }
   319     }
   320   },
   322   // Return currently selected list item node
   323   get selected() {
   324     return document.querySelector(".pref-item.selected");
   325   },
   327   // Set list item node as selected
   328   set selected(aSelection) {
   329     let currentSelection = this.selected;
   330     if (aSelection == currentSelection) {
   331       return;
   332     }
   334     // Clear any previous selection
   335     if (currentSelection) {
   336       currentSelection.classList.remove("selected");
   337       currentSelection.removeEventListener("keypress", this.handleKeypress, false);
   338     }
   340     // Set any current selection
   341     if (aSelection) {
   342       aSelection.classList.add("selected");
   343       aSelection.addEventListener("keypress", this.handleKeypress, false);
   344     }
   345   },
   347   // Watch user key input so we can provide Enter key action, commit input values
   348   handleKeypress: function AC_handleKeypress(aEvent) {
   349     if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
   350       aEvent.target.blur();
   351   },
   353   // Return the target list item node of an action event
   354   getLINodeForEvent: function AC_getLINodeForEvent(aEvent) {
   355     let node = aEvent.target;
   356     while (node && node.nodeName != "li") {
   357       node = node.parentNode;
   358     }
   360     return node;
   361   },
   363   // Return a pref of a list item node
   364   _getPrefForNode: function AC_getPrefForNode(aNode) {
   365     let pref = aNode.getAttribute("name");
   367     return new Pref(pref);
   368   },
   370   // When list item name or value are tapped
   371   selectOrToggleBoolPref: function AC_selectOrToggleBoolPref(aEvent) {
   372     let node = this.getLINodeForEvent(aEvent);
   374     // If not already selected, just do so
   375     if (this.selected != node) {
   376       this.selected = node;
   377       return;
   378     }
   380     // If already selected, and value is boolean, toggle it
   381     let pref = this._getPrefForNode(node);
   382     if (pref.type != Services.prefs.PREF_BOOL) {
   383       return;
   384     }
   386     this.toggleBoolPref(aEvent);
   387   },
   389   // When finalizing list input values due to blur
   390   setIntOrStringPref: function AC_setIntOrStringPref(aEvent) {
   391     let node = this.getLINodeForEvent(aEvent);
   393     // Skip if locked
   394     let pref = this._getPrefForNode(node);
   395     if (pref.locked) {
   396       return;
   397     }
   399     // Boolean inputs blur to remove focus from "button"
   400     if (pref.type == Services.prefs.PREF_BOOL) {
   401       return;
   402     }
   404     // String and Int inputs change / commit on blur
   405     pref.value = aEvent.target.value;
   406   },
   408   // When we reset a pref to it's default value (note resetting a user created pref will delete it)
   409   resetDefaultPref: function AC_resetDefaultPref(aEvent) {
   410     let node = this.getLINodeForEvent(aEvent);
   412     // If not already selected, do so
   413     if (this.selected != node) {
   414       this.selected = node;
   415     }
   417     // Reset will handle any locked condition
   418     let pref = this._getPrefForNode(node);
   419     pref.reset();
   420   },
   422   // When we want to toggle a bool pref
   423   toggleBoolPref: function AC_toggleBoolPref(aEvent) {
   424     let node = this.getLINodeForEvent(aEvent);
   426     // Skip if locked, or not boolean
   427     let pref = this._getPrefForNode(node);
   428     if (pref.locked) {
   429       return;
   430     }
   432     // Toggle, and blur to remove field focus
   433     pref.value = !pref.value;
   434     aEvent.target.blur();
   435   },
   437   // When Int inputs have their Up or Down arrows toggled
   438   incrOrDecrIntPref: function AC_incrOrDecrIntPref(aEvent, aInt) {
   439     let node = this.getLINodeForEvent(aEvent);
   441     // Skip if locked
   442     let pref = this._getPrefForNode(node);
   443     if (pref.locked) {
   444       return;
   445     }
   447     pref.value += aInt;
   448   },
   450   // Observe preference changes
   451   observe: function AC_observe(aSubject, aTopic, aPrefName) {
   452     let pref = new Pref(aPrefName);
   454     // Ignore uninteresting changes, and avoid "private" preferences
   455     if (aTopic != "nsPref:changed") {
   456       return;
   457     }
   459     // If pref type invalid, refresh display as user reset/removed an item from the list
   460     if (pref.type == Services.prefs.PREF_INVALID) {
   461       document.location.reload();
   462       return;
   463     }
   465     // If pref not already in list, refresh display as it's being added
   466     let item = document.querySelector(".pref-item[name=" + pref.name.quote() + "]");
   467     if (!item) {
   468       document.location.reload();
   469       return;
   470     }
   472     // Else we're modifying a pref
   473     item.setAttribute("value", pref.value);
   474     let input = item.querySelector("input");
   475     input.setAttribute("value", pref.value);
   476     input.value = pref.value;
   478     pref.default ?
   479       item.querySelector(".reset").setAttribute("disabled", "true") :
   480       item.querySelector(".reset").removeAttribute("disabled");
   481   }
   482 }
   485 /* ============================== Pref ==============================
   486  *
   487  * Individual Preference object / methods
   488  *
   489  * Defines a Pref object, a document list item tied to Preferences Services
   490  * And the methods by which they interact.
   491  *
   492  */
   493 function Pref(aName) {
   494   this.name = aName;
   495 }
   497 Pref.prototype = {
   498   get type() {
   499     return Services.prefs.getPrefType(this.name);
   500   },
   502   get value() {
   503     switch (this.type) {
   504       case Services.prefs.PREF_BOOL:
   505         return Services.prefs.getBoolPref(this.name);
   506       case Services.prefs.PREF_INT:
   507         return Services.prefs.getIntPref(this.name);
   508       case Services.prefs.PREF_STRING:
   509       default:
   510         return Services.prefs.getCharPref(this.name);
   511     }
   513   },
   515   set value(aPrefValue) {
   516     switch (this.type) {
   517       case Services.prefs.PREF_BOOL:
   518         Services.prefs.setBoolPref(this.name, aPrefValue);
   519         break;
   520       case Services.prefs.PREF_INT:
   521         Services.prefs.setIntPref(this.name, aPrefValue);
   522         break;
   523       case Services.prefs.PREF_STRING:
   524       default:
   525         Services.prefs.setCharPref(this.name, aPrefValue);
   526     }
   527   },
   529   get default() {
   530     return !Services.prefs.prefHasUserValue(this.name);
   531   },
   533   get locked() {
   534     return Services.prefs.prefIsLocked(this.name);
   535   },
   537   reset: function AC_reset() {
   538     Services.prefs.clearUserPref(this.name);
   539   },
   541   test: function AC_test(aValue) {
   542     return aValue ? aValue.test(this.name) : true;
   543   },
   545   // Get existing or create new LI node for the pref
   546   getOrCreateNewLINode: function AC_getOrCreateNewLINode() {
   547     if (!this.li) {
   548       this.li = document.createElement("li");
   550       this.li.className = "pref-item";
   551       this.li.setAttribute("name", this.name);
   553       // Click callback to ensure list item selected even on no-action tap events
   554       this.li.addEventListener("click",
   555         function(aEvent) {
   556           AboutConfig.selected = AboutConfig.getLINodeForEvent(aEvent);
   557         },
   558         false
   559       );
   561       // Create list item outline, bind to object actions
   562       this.li.innerHTML =
   563         "<div class='pref-name' " +
   564             "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" +
   565             this.name +
   566         "</div>" +
   567         "<div class='pref-item-line'>" +
   568           "<input class='pref-value' value='' " +
   569             "onblur='AboutConfig.setIntOrStringPref(event);' " +
   570             "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" +
   571           "</input>" +
   572           "<div class='pref-button reset' " +
   573             "onclick='AboutConfig.resetDefaultPref(event);'>" +
   574             gStringBundle.GetStringFromName("pref.resetButton") +
   575           "</div>" +
   576           "<div class='pref-button toggle' " +
   577             "onclick='AboutConfig.toggleBoolPref(event);'>" +
   578             gStringBundle.GetStringFromName("pref.toggleButton") +
   579           "</div>" +
   580           "<div class='pref-button up' " +
   581             "onclick='AboutConfig.incrOrDecrIntPref(event, 1);'>" +
   582           "</div>" +
   583           "<div class='pref-button down' " +
   584             "onclick='AboutConfig.incrOrDecrIntPref(event, -1);'>" +
   585           "</div>" +
   586         "</div>";
   588       // Delay providing the list item values, until the LI is returned and added to the document
   589       setTimeout(this._valueSetup.bind(this), INNERHTML_VALUE_DELAY);
   590     }
   592     return this.li;
   593   },
   595   // Initialize list item object values
   596   _valueSetup: function AC_valueSetup() {
   598     this.li.setAttribute("type", this.type);
   599     this.li.setAttribute("value", this.value);
   601     let valDiv = this.li.querySelector(".pref-value");
   602     valDiv.value = this.value;
   604     switch(this.type) {
   605       case Services.prefs.PREF_BOOL:
   606         valDiv.setAttribute("type", "button");
   607         this.li.querySelector(".up").setAttribute("disabled", true);
   608         this.li.querySelector(".down").setAttribute("disabled", true);
   609         break;
   610       case Services.prefs.PREF_STRING:
   611         valDiv.setAttribute("type", "text");
   612         this.li.querySelector(".up").setAttribute("disabled", true);
   613         this.li.querySelector(".down").setAttribute("disabled", true);
   614         this.li.querySelector(".toggle").setAttribute("disabled", true);
   615         break;
   616       case Services.prefs.PREF_INT:
   617         valDiv.setAttribute("type", "number");
   618         this.li.querySelector(".toggle").setAttribute("disabled", true);
   619         break;
   620     }
   622     this.li.setAttribute("default", this.default);
   623     if (this.default) {
   624       this.li.querySelector(".reset").setAttribute("disabled", true);
   625     }
   627     if (this.locked) {
   628       valDiv.setAttribute("disabled", this.locked);
   629       this.li.querySelector(".pref-name").setAttribute("locked", true);
   630     }
   631   }
   632 }

mercurial