diff options
Diffstat (limited to 'mobile/android/chrome/geckoview/config.js')
-rw-r--r-- | mobile/android/chrome/geckoview/config.js | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/mobile/android/chrome/geckoview/config.js b/mobile/android/chrome/geckoview/config.js new file mode 100644 index 0000000000..1cf92416ac --- /dev/null +++ b/mobile/android/chrome/geckoview/config.js @@ -0,0 +1,719 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ +"use strict"; + +var Cm = Components.manager; + +const VKB_ENTER_KEY = 13; // User press of VKB enter key +const INITIAL_PAGE_DELAY = 500; // Initial pause on program start for scroll alignment +const PREFS_BUFFER_MAX = 30; // Max prefs buffer size for getPrefsBuffer() +const PAGE_SCROLL_TRIGGER = 200; // Triggers additional getPrefsBuffer() on user scroll-to-bottom +const FILTER_CHANGE_TRIGGER = 200; // Delay between responses to filterInput changes +const INNERHTML_VALUE_DELAY = 100; // Delay before providing prefs innerHTML value + +var gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( + Ci.nsIClipboardHelper +); + +/* ============================== NewPrefDialog ============================== + * + * New Preference Dialog Object and methods + * + * Implements User Interfaces for creation of a single(new) Preference setting + * + */ +var NewPrefDialog = { + _prefsShield: null, + + _newPrefsDialog: null, + _newPrefItem: null, + _prefNameInputElt: null, + _prefTypeSelectElt: null, + + _booleanValue: null, + _booleanToggle: null, + _stringValue: null, + _intValue: null, + + _positiveButton: null, + + get type() { + return this._prefTypeSelectElt.value; + }, + + set type(aType) { + this._prefTypeSelectElt.value = aType; + switch (this._prefTypeSelectElt.value) { + case "boolean": + this._prefTypeSelectElt.selectedIndex = 0; + break; + case "string": + this._prefTypeSelectElt.selectedIndex = 1; + break; + case "int": + this._prefTypeSelectElt.selectedIndex = 2; + break; + } + + this._newPrefItem.setAttribute("typestyle", aType); + }, + + // Init the NewPrefDialog + init: function AC_init() { + this._prefsShield = document.getElementById("prefs-shield"); + + this._newPrefsDialog = document.getElementById("new-pref-container"); + this._newPrefItem = document.getElementById("new-pref-item"); + this._prefNameInputElt = document.getElementById("new-pref-name"); + this._prefTypeSelectElt = document.getElementById("new-pref-type"); + + this._booleanValue = document.getElementById("new-pref-value-boolean"); + this._stringValue = document.getElementById("new-pref-value-string"); + this._intValue = document.getElementById("new-pref-value-int"); + + this._positiveButton = document.getElementById("positive-button"); + }, + + // Called to update positive button to display text ("Create"/"Change), and enabled/disabled status + // As new pref name is initially displayed, re-focused, or modifed during user input + _updatePositiveButton: function AC_updatePositiveButton(aPrefName) { + document.l10n.setAttributes( + this._positiveButton, + "config-new-pref-create-button" + ); + this._positiveButton.setAttribute("disabled", true); + if (aPrefName == "") { + return; + } + + // If item already in list, it's being changed, else added + const item = AboutConfig._list.filter(i => { + return i.name == aPrefName; + }); + if (item.length) { + document.l10n.setAttributes( + this._positiveButton, + "config-new-pref-change-button" + ); + } else { + this._positiveButton.removeAttribute("disabled"); + } + }, + + // When we want to cancel/hide an existing, or show a new pref dialog + toggleShowHide: function AC_toggleShowHide() { + if (this._newPrefsDialog.classList.contains("show")) { + this.hide(); + } else { + this._show(); + } + }, + + // When we want to show the new pref dialog / shield the prefs list + _show: function AC_show() { + this._newPrefsDialog.classList.add("show"); + this._prefsShield.setAttribute("shown", true); + + // Initial default field values + this._prefNameInputElt.value = ""; + this._updatePositiveButton(this._prefNameInputElt.value); + + this.type = "boolean"; + this._booleanValue.value = "false"; + this._stringValue.value = ""; + this._intValue.value = ""; + + this._prefNameInputElt.focus(); + + window.addEventListener("keypress", this.handleKeypress); + }, + + // When we want to cancel/hide the new pref dialog / un-shield the prefs list + hide: function AC_hide() { + this._newPrefsDialog.classList.remove("show"); + this._prefsShield.removeAttribute("shown"); + + window.removeEventListener("keypress", this.handleKeypress); + }, + + // Watch user key input so we can provide Enter key action, commit input values + handleKeypress: function AC_handleKeypress(aEvent) { + // Close our VKB on new pref enter key press + if (aEvent.keyCode == VKB_ENTER_KEY) { + aEvent.target.blur(); + } + }, + + // New prefs create dialog only allows creating a non-existing preference, doesn't allow for + // Changing an existing one on-the-fly, tap existing/displayed line item pref for that + create: function AC_create(aEvent) { + if (this._positiveButton.getAttribute("disabled") == "true") { + return; + } + + switch (this.type) { + case "boolean": + Services.prefs.setBoolPref( + this._prefNameInputElt.value, + !!(this._booleanValue.value == "true") + ); + break; + case "string": + Services.prefs.setCharPref( + this._prefNameInputElt.value, + this._stringValue.value + ); + break; + case "int": + Services.prefs.setIntPref( + this._prefNameInputElt.value, + this._intValue.value + ); + break; + } + + // Ensure pref adds flushed to disk immediately + Services.prefs.savePrefFile(null); + + this.hide(); + }, + + // Display proper positive button text/state on new prefs name input focus + focusName: function AC_focusName(aEvent) { + this._updatePositiveButton(aEvent.target.value); + }, + + // Display proper positive button text/state as user changes new prefs name + updateName: function AC_updateName(aEvent) { + this._updatePositiveButton(aEvent.target.value); + }, + + // In new prefs dialog, bool prefs are <input type="text">, as they aren't yet tied to an + // Actual Services.prefs.*etBoolPref() + toggleBoolValue: function AC_toggleBoolValue() { + this._booleanValue.value = + this._booleanValue.value == "true" ? "false" : "true"; + }, +}; + +/* ============================== AboutConfig ============================== + * + * Main AboutConfig object and methods + * + * Implements User Interfaces for maintenance of a list of Preference settings + * + */ +var AboutConfig = { + contextMenuLINode: null, + filterInput: null, + _filterPrevInput: null, + _filterChangeTimer: null, + _prefsContainer: null, + _loadingContainer: null, + _list: null, + + // Init the main AboutConfig dialog + init: function AC_init() { + this.filterInput = document.getElementById("filter-input"); + this._prefsContainer = document.getElementById("prefs-container"); + this._loadingContainer = document.getElementById("loading-container"); + + const list = Services.prefs.getChildList(""); + this._list = list.sort().map(function AC_getMapPref(aPref) { + return new Pref(aPref); + }, this); + + // Support filtering about:config via a ?filter=<string> param + const match = /[?&]filter=([^&]+)/i.exec(window.location.href); + if (match) { + this.filterInput.value = decodeURIComponent(match[1]); + } + + // Display the current prefs list (retains searchFilter value) + this.bufferFilterInput(); + + // Setup the prefs observers + Services.prefs.addObserver("", this); + }, + + // Uninit the main AboutConfig dialog + uninit: function AC_uninit() { + // Remove the prefs observer + Services.prefs.removeObserver("", this); + }, + + // Clear the filterInput value, to display the entire list + clearFilterInput: function AC_clearFilterInput() { + this.filterInput.value = ""; + this.bufferFilterInput(); + }, + + // Buffer down rapid changes in filterInput value from keyboard + bufferFilterInput: function AC_bufferFilterInput() { + if (this._filterChangeTimer) { + clearTimeout(this._filterChangeTimer); + } + + this._filterChangeTimer = setTimeout(() => { + this._filterChangeTimer = null; + // Display updated prefs list when filterInput value settles + this._displayNewList(); + }, FILTER_CHANGE_TRIGGER); + }, + + // Update displayed list when filterInput value changes + _displayNewList: function AC_displayNewList() { + // This survives the search filter value past a page refresh + this.filterInput.setAttribute("value", this.filterInput.value); + + // Don't start new filter search if same as last + if (this.filterInput.value == this._filterPrevInput) { + return; + } + this._filterPrevInput = this.filterInput.value; + + // Clear list item selection / context menu, prefs list, get first buffer, set scrolling on + this.selected = ""; + this._clearPrefsContainer(); + this._addMorePrefsToContainer(); + window.onscroll = this.onScroll.bind(this); + + // Pause for screen to settle, then ensure at top + setTimeout(() => { + window.scrollTo(0, 0); + }, INITIAL_PAGE_DELAY); + }, + + // Clear the displayed preferences list + _clearPrefsContainer: function AC_clearPrefsContainer() { + // Quick clear the prefsContainer list + const empty = this._prefsContainer.cloneNode(false); + this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer); + this._prefsContainer = empty; + + // Quick clear the prefs li.HTML list + this._list.forEach(function (item) { + delete item.li; + }); + }, + + // Get a small manageable block of prefs items, and add them to the displayed list + _addMorePrefsToContainer: function AC_addMorePrefsToContainer() { + // Create filter regex + const filterExp = this.filterInput.value + ? new RegExp(this.filterInput.value, "i") + : null; + + // Get a new block for the display list + const prefsBuffer = []; + for ( + let i = 0; + i < this._list.length && prefsBuffer.length < PREFS_BUFFER_MAX; + i++ + ) { + if (!this._list[i].li && this._list[i].test(filterExp)) { + prefsBuffer.push(this._list[i]); + } + } + + // Add the new block to the displayed list + for (let i = 0; i < prefsBuffer.length; i++) { + this._prefsContainer.appendChild(prefsBuffer[i].getOrCreateNewLINode()); + } + + // Determine if anything left to add later by scrolling + let anotherPrefsBufferRemains = false; + for (let i = 0; i < this._list.length; i++) { + if (!this._list[i].li && this._list[i].test(filterExp)) { + anotherPrefsBufferRemains = true; + break; + } + } + + if (anotherPrefsBufferRemains) { + // If still more could be displayed, show the throbber + this._loadingContainer.style.display = "block"; + } else { + // If no more could be displayed, hide the throbber, and stop noticing scroll events + this._loadingContainer.style.display = "none"; + window.onscroll = null; + } + }, + + // If scrolling at the bottom, maybe add some more entries + onScroll: function AC_onScroll(aEvent) { + if ( + this._prefsContainer.scrollHeight - + (window.pageYOffset + window.innerHeight) < + PAGE_SCROLL_TRIGGER + ) { + if (!this._filterChangeTimer) { + this._addMorePrefsToContainer(); + } + } + }, + + // Return currently selected list item node + get selected() { + return document.querySelector(".pref-item.selected"); + }, + + // Set list item node as selected + set selected(aSelection) { + const currentSelection = this.selected; + if (aSelection == currentSelection) { + return; + } + + // Clear any previous selection + if (currentSelection) { + currentSelection.classList.remove("selected"); + currentSelection.removeEventListener("keypress", this.handleKeypress); + } + + // Set any current selection + if (aSelection) { + aSelection.classList.add("selected"); + aSelection.addEventListener("keypress", this.handleKeypress); + } + }, + + // Watch user key input so we can provide Enter key action, commit input values + handleKeypress: function AC_handleKeypress(aEvent) { + if (aEvent.keyCode == VKB_ENTER_KEY) { + aEvent.target.blur(); + } + }, + + // Return the target list item node of an action event + getLINodeForEvent: function AC_getLINodeForEvent(aEvent) { + let node = aEvent.target; + while (node && node.nodeName != "li") { + node = node.parentNode; + } + + return node; + }, + + // Return a pref of a list item node + _getPrefForNode: function AC_getPrefForNode(aNode) { + const pref = aNode.getAttribute("name"); + + return new Pref(pref); + }, + + // When list item name or value are tapped + selectOrToggleBoolPref: function AC_selectOrToggleBoolPref(aEvent) { + const node = this.getLINodeForEvent(aEvent); + + // If not already selected, just do so + if (this.selected != node) { + this.selected = node; + return; + } + + // If already selected, and value is boolean, toggle it + const pref = this._getPrefForNode(node); + if (pref.type != Services.prefs.PREF_BOOL) { + return; + } + + this.toggleBoolPref(aEvent); + }, + + // When finalizing list input values due to blur + setIntOrStringPref: function AC_setIntOrStringPref(aEvent) { + const node = this.getLINodeForEvent(aEvent); + + // Skip if locked + const pref = this._getPrefForNode(node); + if (pref.locked) { + return; + } + + // Boolean inputs blur to remove focus from "button" + if (pref.type == Services.prefs.PREF_BOOL) { + return; + } + + // String and Int inputs change / commit on blur + pref.value = aEvent.target.value; + }, + + // When we reset a pref to it's default value (note resetting a user created pref will delete it) + resetDefaultPref: function AC_resetDefaultPref(aEvent) { + const node = this.getLINodeForEvent(aEvent); + + // If not already selected, do so + if (this.selected != node) { + this.selected = node; + } + + // Reset will handle any locked condition + const pref = this._getPrefForNode(node); + pref.reset(); + + // Ensure pref reset flushed to disk immediately + Services.prefs.savePrefFile(null); + }, + + // When we want to toggle a bool pref + toggleBoolPref: function AC_toggleBoolPref(aEvent) { + const node = this.getLINodeForEvent(aEvent); + + // Skip if locked, or not boolean + const pref = this._getPrefForNode(node); + if (pref.locked) { + return; + } + + // Toggle, and blur to remove field focus + pref.value = !pref.value; + aEvent.target.blur(); + }, + + // When Int inputs have their Up or Down arrows toggled + incrOrDecrIntPref: function AC_incrOrDecrIntPref(aEvent, aInt) { + const node = this.getLINodeForEvent(aEvent); + + // Skip if locked + const pref = this._getPrefForNode(node); + if (pref.locked) { + return; + } + + pref.value += aInt; + }, + + // Observe preference changes + observe: function AC_observe(aSubject, aTopic, aPrefName) { + const pref = new Pref(aPrefName); + + // Ignore uninteresting changes, and avoid "private" preferences + if (aTopic != "nsPref:changed") { + return; + } + + // If pref type invalid, refresh display as user reset/removed an item from the list + if (pref.type == Services.prefs.PREF_INVALID) { + document.location.reload(); + return; + } + + // If pref onscreen, update in place. + const item = document.querySelector( + '.pref-item[name="' + CSS.escape(pref.name) + '"]' + ); + if (item) { + item.setAttribute("value", pref.value); + const input = item.querySelector("input"); + input.setAttribute("value", pref.value); + input.value = pref.value; + + pref.default + ? item.querySelector(".reset").setAttribute("disabled", "true") + : item.querySelector(".reset").removeAttribute("disabled"); + return; + } + + // If pref not already in list, refresh display as it's being added + const anyWhere = this._list.filter(i => { + return i.name == pref.name; + }); + if (!anyWhere.length) { + document.location.reload(); + } + }, + + // Quick context menu helpers for about:config + clipboardCopy: function AC_clipboardCopy(aField) { + const pref = this._getPrefForNode(this.contextMenuLINode); + if (aField == "name") { + gClipboardHelper.copyString(pref.name); + } else { + gClipboardHelper.copyString(pref.value); + } + }, +}; + +/* ============================== Pref ============================== + * + * Individual Preference object / methods + * + * Defines a Pref object, a document list item tied to Preferences Services + * And the methods by which they interact. + * + */ +function Pref(aName) { + this.name = aName; +} + +Pref.prototype = { + get type() { + return Services.prefs.getPrefType(this.name); + }, + + get value() { + switch (this.type) { + case Services.prefs.PREF_BOOL: + return Services.prefs.getBoolPref(this.name); + case Services.prefs.PREF_INT: + return Services.prefs.getIntPref(this.name); + case Services.prefs.PREF_STRING: + default: + return Services.prefs.getCharPref(this.name); + } + }, + set value(aPrefValue) { + switch (this.type) { + case Services.prefs.PREF_BOOL: + Services.prefs.setBoolPref(this.name, aPrefValue); + break; + case Services.prefs.PREF_INT: + Services.prefs.setIntPref(this.name, aPrefValue); + break; + case Services.prefs.PREF_STRING: + default: + Services.prefs.setCharPref(this.name, aPrefValue); + } + + // Ensure pref change flushed to disk immediately + Services.prefs.savePrefFile(null); + }, + + get default() { + return !Services.prefs.prefHasUserValue(this.name); + }, + + get locked() { + return Services.prefs.prefIsLocked(this.name); + }, + + reset: function AC_reset() { + Services.prefs.clearUserPref(this.name); + }, + + test: function AC_test(aValue) { + return aValue ? aValue.test(this.name) : true; + }, + + // Get existing or create new LI node for the pref + getOrCreateNewLINode: function AC_getOrCreateNewLINode() { + if (!this.li) { + this.li = document.createElement("li"); + + this.li.className = "pref-item"; + this.li.setAttribute("name", this.name); + + // Click callback to ensure list item selected even on no-action tap events + this.li.addEventListener("click", function (aEvent) { + AboutConfig.selected = AboutConfig.getLINodeForEvent(aEvent); + }); + + // Contextmenu callback to identify selected list item + this.li.addEventListener("contextmenu", function (aEvent) { + AboutConfig.contextMenuLINode = AboutConfig.getLINodeForEvent(aEvent); + }); + + this.li.setAttribute("contextmenu", "prefs-context-menu"); + + const prefName = document.createElement("div"); + prefName.className = "pref-name"; + prefName.addEventListener("click", function (event) { + AboutConfig.selectOrToggleBoolPref(event); + }); + prefName.textContent = this.name; + + this.li.appendChild(prefName); + + const prefItemLine = document.createElement("div"); + prefItemLine.className = "pref-item-line"; + + const prefValue = document.createElement("input"); + prefValue.className = "pref-value"; + prefValue.addEventListener("blur", function (event) { + AboutConfig.setIntOrStringPref(event); + }); + prefValue.addEventListener("click", function (event) { + AboutConfig.selectOrToggleBoolPref(event); + }); + prefValue.value = ""; + prefItemLine.appendChild(prefValue); + + const resetButton = document.createElement("div"); + resetButton.className = "pref-button reset"; + resetButton.addEventListener("click", function (event) { + AboutConfig.resetDefaultPref(event); + }); + resetButton.setAttribute("data-l10n-id", "config-pref-reset-button"); + prefItemLine.appendChild(resetButton); + + const toggleButton = document.createElement("div"); + toggleButton.className = "pref-button toggle"; + toggleButton.addEventListener("click", function (event) { + AboutConfig.toggleBoolPref(event); + }); + toggleButton.setAttribute("data-l10n-id", "config-pref-toggle-button"); + prefItemLine.appendChild(toggleButton); + + const upButton = document.createElement("div"); + upButton.className = "pref-button up"; + upButton.addEventListener("click", function (event) { + AboutConfig.incrOrDecrIntPref(event, 1); + }); + prefItemLine.appendChild(upButton); + + const downButton = document.createElement("div"); + downButton.className = "pref-button down"; + downButton.addEventListener("click", function (event) { + AboutConfig.incrOrDecrIntPref(event, -1); + }); + prefItemLine.appendChild(downButton); + + this.li.appendChild(prefItemLine); + + // Delay providing the list item values, until the LI is returned and added to the document + setTimeout(this._valueSetup.bind(this), INNERHTML_VALUE_DELAY); + } + + return this.li; + }, + + // Initialize list item object values + _valueSetup: function AC_valueSetup() { + this.li.setAttribute("type", this.type); + this.li.setAttribute("value", this.value); + + const valDiv = this.li.querySelector(".pref-value"); + valDiv.value = this.value; + + switch (this.type) { + case Services.prefs.PREF_BOOL: + valDiv.setAttribute("type", "button"); + this.li.querySelector(".up").setAttribute("disabled", true); + this.li.querySelector(".down").setAttribute("disabled", true); + break; + case Services.prefs.PREF_STRING: + valDiv.setAttribute("type", "text"); + this.li.querySelector(".up").setAttribute("disabled", true); + this.li.querySelector(".down").setAttribute("disabled", true); + this.li.querySelector(".toggle").setAttribute("disabled", true); + break; + case Services.prefs.PREF_INT: + valDiv.setAttribute("type", "number"); + this.li.querySelector(".toggle").setAttribute("disabled", true); + break; + } + + this.li.setAttribute("default", this.default); + if (this.default) { + this.li.querySelector(".reset").setAttribute("disabled", true); + } + + if (this.locked) { + valDiv.setAttribute("disabled", this.locked); + this.li.querySelector(".pref-name").setAttribute("locked", true); + } + }, +}; |