mobile/android/chrome/content/config.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:a6eda67982b1
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";
5
6 const {classes: Cc, interfaces: Ci, manager: Cm, utils: Cu} = Components;
7 Cu.import("resource://gre/modules/Services.jsm");
8
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
15
16 let gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties");
17 let gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
18
19
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 = {
28
29 _prefsShield: null,
30
31 _newPrefsDialog: null,
32 _newPrefItem: null,
33 _prefNameInputElt: null,
34 _prefTypeSelectElt: null,
35
36 _booleanValue: null,
37 _booleanToggle: null,
38 _stringValue: null,
39 _intValue: null,
40
41 _positiveButton: null,
42
43 get type() {
44 return this._prefTypeSelectElt.value;
45 },
46
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 }
60
61 this._newPrefItem.setAttribute("typestyle", aType);
62 },
63
64 // Init the NewPrefDialog
65 init: function AC_init() {
66 this._prefsShield = document.getElementById("prefs-shield");
67
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");
72
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");
76
77 this._positiveButton = document.getElementById("positive-button");
78 },
79
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 }
88
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 },
97
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 },
106
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);
111
112 // Initial default field values
113 this._prefNameInputElt.value = "";
114 this._updatePositiveButton(this._prefNameInputElt.value);
115
116 this.type = "boolean";
117 this._booleanValue.value = "false";
118 this._stringValue.value = "";
119 this._intValue.value = "";
120
121 this._prefNameInputElt.focus();
122
123 window.addEventListener("keypress", this.handleKeypress, false);
124 },
125
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");
130
131 window.removeEventListener("keypress", this.handleKeypress, false);
132 },
133
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 },
140
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 }
147
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 }
159
160 this.hide();
161 },
162
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 },
167
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 },
172
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 }
179
180
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 = {
189
190 contextMenuLINode: null,
191 filterInput: null,
192 _filterPrevInput: null,
193 _filterChangeTimer: null,
194 _prefsContainer: null,
195 _loadingContainer: null,
196 _list: null,
197
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");
203
204 let list = Services.prefs.getChildList("");
205 this._list = list.sort().map( function AC_getMapPref(aPref) {
206 return new Pref(aPref);
207 }, this);
208
209 // Display the current prefs list (retains searchFilter value)
210 this.bufferFilterInput();
211
212 // Setup the prefs observers
213 Services.prefs.addObserver("", this, false);
214 },
215
216 // Uninit the main AboutConfig dialog
217 uninit: function AC_uninit() {
218 // Remove the prefs observer
219 Services.prefs.removeObserver("", this);
220
221 // Ensure pref adds/changes/resets flushed to disk on unload
222 Services.prefs.savePrefFile(null);
223 },
224
225 // Clear the filterInput value, to display the entire list
226 clearFilterInput: function AC_clearFilterInput() {
227 this.filterInput.value = "";
228 this.bufferFilterInput();
229 },
230
231 // Buffer down rapid changes in filterInput value from keyboard
232 bufferFilterInput: function AC_bufferFilterInput() {
233 if (this._filterChangeTimer) {
234 clearTimeout(this._filterChangeTimer);
235 }
236
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 },
243
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);
248
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;
254
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);
260
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 },
266
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;
273
274 // Quick clear the prefs li.HTML list
275 this._list.forEach(function(item) {
276 delete item.li;
277 });
278 },
279
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;
285
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 }
293
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 }
298
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 }
307
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 },
317
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 },
326
327
328 // Return currently selected list item node
329 get selected() {
330 return document.querySelector(".pref-item.selected");
331 },
332
333 // Set list item node as selected
334 set selected(aSelection) {
335 let currentSelection = this.selected;
336 if (aSelection == currentSelection) {
337 return;
338 }
339
340 // Clear any previous selection
341 if (currentSelection) {
342 currentSelection.classList.remove("selected");
343 currentSelection.removeEventListener("keypress", this.handleKeypress, false);
344 }
345
346 // Set any current selection
347 if (aSelection) {
348 aSelection.classList.add("selected");
349 aSelection.addEventListener("keypress", this.handleKeypress, false);
350 }
351 },
352
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 },
358
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 }
365
366 return node;
367 },
368
369 // Return a pref of a list item node
370 _getPrefForNode: function AC_getPrefForNode(aNode) {
371 let pref = aNode.getAttribute("name");
372
373 return new Pref(pref);
374 },
375
376 // When list item name or value are tapped
377 selectOrToggleBoolPref: function AC_selectOrToggleBoolPref(aEvent) {
378 let node = this.getLINodeForEvent(aEvent);
379
380 // If not already selected, just do so
381 if (this.selected != node) {
382 this.selected = node;
383 return;
384 }
385
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 }
391
392 this.toggleBoolPref(aEvent);
393 },
394
395 // When finalizing list input values due to blur
396 setIntOrStringPref: function AC_setIntOrStringPref(aEvent) {
397 let node = this.getLINodeForEvent(aEvent);
398
399 // Skip if locked
400 let pref = this._getPrefForNode(node);
401 if (pref.locked) {
402 return;
403 }
404
405 // Boolean inputs blur to remove focus from "button"
406 if (pref.type == Services.prefs.PREF_BOOL) {
407 return;
408 }
409
410 // String and Int inputs change / commit on blur
411 pref.value = aEvent.target.value;
412 },
413
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);
417
418 // If not already selected, do so
419 if (this.selected != node) {
420 this.selected = node;
421 }
422
423 // Reset will handle any locked condition
424 let pref = this._getPrefForNode(node);
425 pref.reset();
426 },
427
428 // When we want to toggle a bool pref
429 toggleBoolPref: function AC_toggleBoolPref(aEvent) {
430 let node = this.getLINodeForEvent(aEvent);
431
432 // Skip if locked, or not boolean
433 let pref = this._getPrefForNode(node);
434 if (pref.locked) {
435 return;
436 }
437
438 // Toggle, and blur to remove field focus
439 pref.value = !pref.value;
440 aEvent.target.blur();
441 },
442
443 // When Int inputs have their Up or Down arrows toggled
444 incrOrDecrIntPref: function AC_incrOrDecrIntPref(aEvent, aInt) {
445 let node = this.getLINodeForEvent(aEvent);
446
447 // Skip if locked
448 let pref = this._getPrefForNode(node);
449 if (pref.locked) {
450 return;
451 }
452
453 pref.value += aInt;
454 },
455
456 // Observe preference changes
457 observe: function AC_observe(aSubject, aTopic, aPrefName) {
458 let pref = new Pref(aPrefName);
459
460 // Ignore uninteresting changes, and avoid "private" preferences
461 if (aTopic != "nsPref:changed") {
462 return;
463 }
464
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 }
470
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 }
477
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;
483
484 pref.default ?
485 item.querySelector(".reset").setAttribute("disabled", "true") :
486 item.querySelector(".reset").removeAttribute("disabled");
487 },
488
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 }
499
500
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 }
512
513 Pref.prototype = {
514 get type() {
515 return Services.prefs.getPrefType(this.name);
516 },
517
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 }
528
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 },
543
544 get default() {
545 return !Services.prefs.prefHasUserValue(this.name);
546 },
547
548 get locked() {
549 return Services.prefs.prefIsLocked(this.name);
550 },
551
552 reset: function AC_reset() {
553 Services.prefs.clearUserPref(this.name);
554 },
555
556 test: function AC_test(aValue) {
557 return aValue ? aValue.test(this.name) : true;
558 },
559
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");
564
565 this.li.className = "pref-item";
566 this.li.setAttribute("name", this.name);
567
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 );
575
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 );
583
584 this.li.setAttribute("contextmenu", "prefs-context-menu");
585
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>";
612
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 }
616
617 return this.li;
618 },
619
620 // Initialize list item object values
621 _valueSetup: function AC_valueSetup() {
622
623 this.li.setAttribute("type", this.type);
624 this.li.setAttribute("value", this.value);
625
626 let valDiv = this.li.querySelector(".pref-value");
627 valDiv.value = this.value;
628
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 }
646
647 this.li.setAttribute("default", this.default);
648 if (this.default) {
649 this.li.querySelector(".reset").setAttribute("disabled", true);
650 }
651
652 if (this.locked) {
653 valDiv.setAttribute("disabled", this.locked);
654 this.li.querySelector(".pref-name").setAttribute("locked", true);
655 }
656 }
657 }
658

mercurial