toolkit/components/viewconfig/content/config.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:3209590d9bee
1 // -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 Components.utils.import("resource://gre/modules/Services.jsm");
8
9 const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
10 const nsISupportsString = Components.interfaces.nsISupportsString;
11 const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
12 const nsIClipboardHelper = Components.interfaces.nsIClipboardHelper;
13 const nsIAtomService = Components.interfaces.nsIAtomService;
14
15 const nsSupportsString_CONTRACTID = "@mozilla.org/supports-string;1";
16 const nsPrompt_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
17 const nsPrefService_CONTRACTID = "@mozilla.org/preferences-service;1";
18 const nsClipboardHelper_CONTRACTID = "@mozilla.org/widget/clipboardhelper;1";
19 const nsAtomService_CONTRACTID = "@mozilla.org/atom-service;1";
20
21 const gPrefBranch = Services.prefs;
22 const gClipboardHelper = Components.classes[nsClipboardHelper_CONTRACTID].getService(nsIClipboardHelper);
23 const gAtomService = Components.classes[nsAtomService_CONTRACTID].getService(nsIAtomService);
24
25 var gLockProps = ["default", "user", "locked"];
26 // we get these from a string bundle
27 var gLockStrs = [];
28 var gTypeStrs = [];
29
30 const PREF_IS_DEFAULT_VALUE = 0;
31 const PREF_IS_USER_SET = 1;
32 const PREF_IS_LOCKED = 2;
33
34 var gPrefHash = {};
35 var gPrefArray = [];
36 var gPrefView = gPrefArray; // share the JS array
37 var gSortedColumn = "prefCol";
38 var gSortFunction = null;
39 var gSortDirection = 1; // 1 is ascending; -1 is descending
40 var gConfigBundle = null;
41 var gFilter = null;
42
43 var view = {
44 get rowCount() { return gPrefView.length; },
45 getCellText : function(index, col) {
46 if (!(index in gPrefView))
47 return "";
48
49 var value = gPrefView[index][col.id];
50
51 switch (col.id) {
52 case "lockCol":
53 return gLockStrs[value];
54 case "typeCol":
55 return gTypeStrs[value];
56 default:
57 return value;
58 }
59 },
60 getRowProperties : function(index) { return ""; },
61 getCellProperties : function(index, col) {
62 if (index in gPrefView)
63 return gLockProps[gPrefView[index].lockCol];
64
65 return "";
66 },
67 getColumnProperties : function(col) { return ""; },
68 treebox : null,
69 selection : null,
70 isContainer : function(index) { return false; },
71 isContainerOpen : function(index) { return false; },
72 isContainerEmpty : function(index) { return false; },
73 isSorted : function() { return true; },
74 canDrop : function(index, orientation) { return false; },
75 drop : function(row, orientation) {},
76 setTree : function(out) { this.treebox = out; },
77 getParentIndex: function(rowIndex) { return -1; },
78 hasNextSibling: function(rowIndex, afterIndex) { return false; },
79 getLevel: function(index) { return 1; },
80 getImageSrc: function(row, col) { return ""; },
81 toggleOpenState : function(index) {},
82 cycleHeader: function(col) {
83 var index = this.selection.currentIndex;
84 if (col.id == gSortedColumn) {
85 gSortDirection = -gSortDirection;
86 gPrefArray.reverse();
87 if (gPrefView != gPrefArray)
88 gPrefView.reverse();
89 if (index >= 0)
90 index = gPrefView.length - index - 1;
91 }
92 else {
93 var pref = null;
94 if (index >= 0)
95 pref = gPrefView[index];
96
97 var old = document.getElementById(gSortedColumn);
98 old.setAttribute("sortDirection", "");
99 gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
100 if (gPrefView != gPrefArray)
101 gPrefView.sort(gSortFunction);
102 gSortedColumn = col.id;
103 if (pref)
104 index = getViewIndexOfPref(pref);
105 }
106 col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
107 this.treebox.invalidate();
108 if (index >= 0) {
109 this.selection.select(index);
110 this.treebox.ensureRowIsVisible(index);
111 }
112 },
113 selectionChanged : function() {},
114 cycleCell: function(row, col) {},
115 isEditable: function(row, col) {return false; },
116 isSelectable: function(row, col) {return false; },
117 setCellValue: function(row, col, value) {},
118 setCellText: function(row, col, value) {},
119 performAction: function(action) {},
120 performActionOnRow: function(action, row) {},
121 performActionOnCell: function(action, row, col) {},
122 isSeparator: function(index) {return false; }
123 };
124
125 // find the index in gPrefView of a pref object
126 // or -1 if it does not exist in the filtered view
127 function getViewIndexOfPref(pref)
128 {
129 var low = -1, high = gPrefView.length;
130 var index = (low + high) >> 1;
131 while (index > low) {
132 var mid = gPrefView[index];
133 if (mid == pref)
134 return index;
135 if (gSortFunction(mid, pref) < 0)
136 low = index;
137 else
138 high = index;
139 index = (low + high) >> 1;
140 }
141 return -1;
142 }
143
144 // find the index in gPrefView where a pref object belongs
145 function getNearestViewIndexOfPref(pref)
146 {
147 var low = -1, high = gPrefView.length;
148 var index = (low + high) >> 1;
149 while (index > low) {
150 if (gSortFunction(gPrefView[index], pref) < 0)
151 low = index;
152 else
153 high = index;
154 index = (low + high) >> 1;
155 }
156 return high;
157 }
158
159 // find the index in gPrefArray of a pref object
160 function getIndexOfPref(pref)
161 {
162 var low = -1, high = gPrefArray.length;
163 var index = (low + high) >> 1;
164 while (index > low) {
165 var mid = gPrefArray[index];
166 if (mid == pref)
167 return index;
168 if (gSortFunction(mid, pref) < 0)
169 low = index;
170 else
171 high = index;
172 index = (low + high) >> 1;
173 }
174 return index;
175 }
176
177 function getNearestIndexOfPref(pref)
178 {
179 var low = -1, high = gPrefArray.length;
180 var index = (low + high) >> 1;
181 while (index > low) {
182 if (gSortFunction(gPrefArray[index], pref) < 0)
183 low = index;
184 else
185 high = index;
186 index = (low + high) >> 1;
187 }
188 return high;
189 }
190
191 var gPrefListener =
192 {
193 observe: function(subject, topic, prefName)
194 {
195 if (topic != "nsPref:changed")
196 return;
197
198 var arrayIndex = gPrefArray.length;
199 var viewIndex = arrayIndex;
200 var selectedIndex = view.selection.currentIndex;
201 var pref;
202 var updateView = false;
203 var updateArray = false;
204 var addedRow = false;
205 if (prefName in gPrefHash) {
206 pref = gPrefHash[prefName];
207 viewIndex = getViewIndexOfPref(pref);
208 arrayIndex = getIndexOfPref(pref);
209 fetchPref(prefName, arrayIndex);
210 // fetchPref replaces the existing pref object
211 pref = gPrefHash[prefName];
212 if (viewIndex >= 0) {
213 // Might need to update the filtered view
214 gPrefView[viewIndex] = gPrefHash[prefName];
215 view.treebox.invalidateRow(viewIndex);
216 }
217 if (gSortedColumn == "lockCol" || gSortedColumn == "valueCol") {
218 updateArray = true;
219 gPrefArray.splice(arrayIndex, 1);
220 if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
221 updateView = true;
222 gPrefView.splice(viewIndex, 1);
223 }
224 }
225 }
226 else {
227 fetchPref(prefName, arrayIndex);
228 pref = gPrefArray.pop();
229 updateArray = true;
230 addedRow = true;
231 if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
232 updateView = true;
233 }
234 }
235 if (updateArray) {
236 // Reinsert in the data array
237 var newIndex = getNearestIndexOfPref(pref);
238 gPrefArray.splice(newIndex, 0, pref);
239
240 if (updateView) {
241 // View is filtered, reinsert in the view separately
242 newIndex = getNearestViewIndexOfPref(pref);
243 gPrefView.splice(newIndex, 0, pref);
244 }
245 else if (gFilter) {
246 // View is filtered, but nothing to update
247 return;
248 }
249
250 if (addedRow)
251 view.treebox.rowCountChanged(newIndex, 1);
252
253 // Invalidate the changed range in the view
254 var low = Math.min(viewIndex, newIndex);
255 var high = Math.max(viewIndex, newIndex);
256 view.treebox.invalidateRange(low, high);
257
258 if (selectedIndex == viewIndex) {
259 selectedIndex = newIndex;
260 }
261 else if (selectedIndex >= low && selectedIndex <= high) {
262 selectedIndex += (newIndex > viewIndex) ? -1 : 1;
263 }
264 if (selectedIndex >= 0) {
265 view.selection.select(selectedIndex);
266 if (selectedIndex == newIndex)
267 view.treebox.ensureRowIsVisible(selectedIndex);
268 }
269 }
270 }
271 };
272
273 function prefObject(prefName, prefIndex)
274 {
275 this.prefCol = prefName;
276 }
277
278 prefObject.prototype =
279 {
280 lockCol: PREF_IS_DEFAULT_VALUE,
281 typeCol: nsIPrefBranch.PREF_STRING,
282 valueCol: ""
283 };
284
285 function fetchPref(prefName, prefIndex)
286 {
287 var pref = new prefObject(prefName);
288
289 gPrefHash[prefName] = pref;
290 gPrefArray[prefIndex] = pref;
291
292 if (gPrefBranch.prefIsLocked(prefName))
293 pref.lockCol = PREF_IS_LOCKED;
294 else if (gPrefBranch.prefHasUserValue(prefName))
295 pref.lockCol = PREF_IS_USER_SET;
296
297 try {
298 switch (gPrefBranch.getPrefType(prefName)) {
299 case gPrefBranch.PREF_BOOL:
300 pref.typeCol = gPrefBranch.PREF_BOOL;
301 // convert to a string
302 pref.valueCol = gPrefBranch.getBoolPref(prefName).toString();
303 break;
304 case gPrefBranch.PREF_INT:
305 pref.typeCol = gPrefBranch.PREF_INT;
306 // convert to a string
307 pref.valueCol = gPrefBranch.getIntPref(prefName).toString();
308 break;
309 default:
310 case gPrefBranch.PREF_STRING:
311 pref.valueCol = gPrefBranch.getComplexValue(prefName, nsISupportsString).data;
312 // Try in case it's a localized string (will throw an exception if not)
313 if (pref.lockCol == PREF_IS_DEFAULT_VALUE &&
314 /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.valueCol))
315 pref.valueCol = gPrefBranch.getComplexValue(prefName, nsIPrefLocalizedString).data;
316 break;
317 }
318 } catch (e) {
319 // Also catch obscure cases in which you can't tell in advance
320 // that the pref exists but has no user or default value...
321 }
322 }
323
324 function onConfigLoad()
325 {
326 // Load strings
327 gConfigBundle = document.getElementById("configBundle");
328
329 gLockStrs[PREF_IS_DEFAULT_VALUE] = gConfigBundle.getString("default");
330 gLockStrs[PREF_IS_USER_SET] = gConfigBundle.getString("user");
331 gLockStrs[PREF_IS_LOCKED] = gConfigBundle.getString("locked");
332
333 gTypeStrs[nsIPrefBranch.PREF_STRING] = gConfigBundle.getString("string");
334 gTypeStrs[nsIPrefBranch.PREF_INT] = gConfigBundle.getString("int");
335 gTypeStrs[nsIPrefBranch.PREF_BOOL] = gConfigBundle.getString("bool");
336
337 var showWarning = gPrefBranch.getBoolPref("general.warnOnAboutConfig");
338
339 if (showWarning)
340 document.getElementById("warningButton").focus();
341 else
342 ShowPrefs();
343 }
344
345 // Unhide the warning message
346 function ShowPrefs()
347 {
348 gPrefBranch.getChildList("").forEach(fetchPref);
349
350 var descending = document.getElementsByAttribute("sortDirection", "descending");
351 if (descending.item(0)) {
352 gSortedColumn = descending[0].id;
353 gSortDirection = -1;
354 }
355 else {
356 var ascending = document.getElementsByAttribute("sortDirection", "ascending");
357 if (ascending.item(0))
358 gSortedColumn = ascending[0].id;
359 else
360 document.getElementById(gSortedColumn).setAttribute("sortDirection", "ascending");
361 }
362 gSortFunction = gSortFunctions[gSortedColumn];
363 gPrefArray.sort(gSortFunction);
364
365 gPrefBranch.addObserver("", gPrefListener, false);
366
367 var configTree = document.getElementById("configTree");
368 configTree.view = view;
369 configTree.controllers.insertControllerAt(0, configController);
370
371 document.getElementById("configDeck").setAttribute("selectedIndex", 1);
372 document.getElementById("configTreeKeyset").removeAttribute("disabled");
373 if (!document.getElementById("showWarningNextTime").checked)
374 gPrefBranch.setBoolPref("general.warnOnAboutConfig", false);
375
376 // Process about:config?filter=<string>
377 var textbox = document.getElementById("textbox");
378 // About URIs don't support query params, so do this manually
379 var loc = document.location.href;
380 var matches = /[?&]filter\=([^&]+)/i.exec(loc);
381 if (matches)
382 textbox.value = decodeURIComponent(matches[1]);
383
384 // Even if we did not set the filter string via the URL query,
385 // textbox might have been set via some other mechanism
386 if (textbox.value)
387 FilterPrefs();
388 textbox.focus();
389 }
390
391 function onConfigUnload()
392 {
393 if (document.getElementById("configDeck").getAttribute("selectedIndex") == 1) {
394 gPrefBranch.removeObserver("", gPrefListener);
395 var configTree = document.getElementById("configTree");
396 configTree.view = null;
397 configTree.controllers.removeController(configController);
398 }
399 }
400
401 function FilterPrefs()
402 {
403 if (document.getElementById("configDeck").getAttribute("selectedIndex") != 1) {
404 return;
405 }
406
407 var substring = document.getElementById("textbox").value;
408 // Check for "/regex/[i]"
409 if (substring.charAt(0) == '/') {
410 var r = substring.match(/^\/(.*)\/(i?)$/);
411 try {
412 gFilter = RegExp(r[1], r[2]);
413 }
414 catch (e) {
415 return; // Do nothing on incomplete or bad RegExp
416 }
417 }
418 else if (substring) {
419 gFilter = RegExp(substring.replace(/([^* \w])/g, "\\$1")
420 .replace(/^\*+/, "").replace(/\*+/g, ".*"), "i");
421 } else {
422 gFilter = null;
423 }
424
425 var prefCol = (view.selection && view.selection.currentIndex < 0) ?
426 null : gPrefView[view.selection.currentIndex].prefCol;
427 var oldlen = gPrefView.length;
428 gPrefView = gPrefArray;
429 if (gFilter) {
430 gPrefView = [];
431 for (var i = 0; i < gPrefArray.length; ++i)
432 if (gFilter.test(gPrefArray[i].prefCol + ";" + gPrefArray[i].valueCol))
433 gPrefView.push(gPrefArray[i]);
434 }
435 view.treebox.invalidate();
436 view.treebox.rowCountChanged(oldlen, gPrefView.length - oldlen);
437 gotoPref(prefCol);
438 }
439
440 function prefColSortFunction(x, y)
441 {
442 if (x.prefCol > y.prefCol)
443 return gSortDirection;
444 if (x.prefCol < y.prefCol)
445 return -gSortDirection;
446 return 0;
447 }
448
449 function lockColSortFunction(x, y)
450 {
451 if (x.lockCol != y.lockCol)
452 return gSortDirection * (y.lockCol - x.lockCol);
453 return prefColSortFunction(x, y);
454 }
455
456 function typeColSortFunction(x, y)
457 {
458 if (x.typeCol != y.typeCol)
459 return gSortDirection * (y.typeCol - x.typeCol);
460 return prefColSortFunction(x, y);
461 }
462
463 function valueColSortFunction(x, y)
464 {
465 if (x.valueCol > y.valueCol)
466 return gSortDirection;
467 if (x.valueCol < y.valueCol)
468 return -gSortDirection;
469 return prefColSortFunction(x, y);
470 }
471
472 const gSortFunctions =
473 {
474 prefCol: prefColSortFunction,
475 lockCol: lockColSortFunction,
476 typeCol: typeColSortFunction,
477 valueCol: valueColSortFunction
478 };
479
480 const configController = {
481 supportsCommand: function supportsCommand(command) {
482 return command == "cmd_copy";
483 },
484 isCommandEnabled: function isCommandEnabled(command) {
485 return view.selection && view.selection.currentIndex >= 0;
486 },
487 doCommand: function doCommand(command) {
488 copyPref();
489 },
490 onEvent: function onEvent(event) {
491 }
492 }
493
494 function updateContextMenu()
495 {
496 var lockCol = PREF_IS_LOCKED;
497 var typeCol = nsIPrefBranch.PREF_STRING;
498 var valueCol = "";
499 var copyDisabled = true;
500 var prefSelected = view.selection.currentIndex >= 0;
501
502 if (prefSelected) {
503 var prefRow = gPrefView[view.selection.currentIndex];
504 lockCol = prefRow.lockCol;
505 typeCol = prefRow.typeCol;
506 valueCol = prefRow.valueCol;
507 copyDisabled = false;
508 }
509
510 var copyPref = document.getElementById("copyPref");
511 copyPref.setAttribute("disabled", copyDisabled);
512
513 var copyName = document.getElementById("copyName");
514 copyName.setAttribute("disabled", copyDisabled);
515
516 var copyValue = document.getElementById("copyValue");
517 copyValue.setAttribute("disabled", copyDisabled);
518
519 var resetSelected = document.getElementById("resetSelected");
520 resetSelected.setAttribute("disabled", lockCol != PREF_IS_USER_SET);
521
522 var canToggle = typeCol == nsIPrefBranch.PREF_BOOL && valueCol != "";
523 // indicates that a pref is locked or no pref is selected at all
524 var isLocked = lockCol == PREF_IS_LOCKED;
525
526 var modifySelected = document.getElementById("modifySelected");
527 modifySelected.setAttribute("disabled", isLocked);
528 modifySelected.hidden = canToggle;
529
530 var toggleSelected = document.getElementById("toggleSelected");
531 toggleSelected.setAttribute("disabled", isLocked);
532 toggleSelected.hidden = !canToggle;
533 }
534
535 function copyPref()
536 {
537 var pref = gPrefView[view.selection.currentIndex];
538 gClipboardHelper.copyString(pref.prefCol + ';' + pref.valueCol, document);
539 }
540
541 function copyName()
542 {
543 gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].prefCol, document);
544 }
545
546 function copyValue()
547 {
548 gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].valueCol, document);
549 }
550
551 function ModifySelected()
552 {
553 if (view.selection.currentIndex >= 0)
554 ModifyPref(gPrefView[view.selection.currentIndex]);
555 }
556
557 function ResetSelected()
558 {
559 var entry = gPrefView[view.selection.currentIndex];
560 gPrefBranch.clearUserPref(entry.prefCol);
561 }
562
563 function NewPref(type)
564 {
565 var result = { value: "" };
566 var dummy = { value: 0 };
567 if (Services.prompt.prompt(window,
568 gConfigBundle.getFormattedString("new_title",
569 [gTypeStrs[type]]),
570 gConfigBundle.getString("new_prompt"),
571 result,
572 null,
573 dummy)) {
574 result.value = result.value.trim();
575 if (!result.value) {
576 return;
577 }
578
579 var pref;
580 if (result.value in gPrefHash)
581 pref = gPrefHash[result.value];
582 else
583 pref = { prefCol: result.value, lockCol: PREF_IS_DEFAULT_VALUE, typeCol: type, valueCol: "" };
584 if (ModifyPref(pref))
585 setTimeout(gotoPref, 0, result.value);
586 }
587 }
588
589 function gotoPref(pref)
590 {
591 // make sure the pref exists and is displayed in the current view
592 var index = pref in gPrefHash ? getViewIndexOfPref(gPrefHash[pref]) : -1;
593 if (index >= 0) {
594 view.selection.select(index);
595 view.treebox.ensureRowIsVisible(index);
596 } else {
597 view.selection.clearSelection();
598 view.selection.currentIndex = -1;
599 }
600 }
601
602 function ModifyPref(entry)
603 {
604 if (entry.lockCol == PREF_IS_LOCKED)
605 return false;
606 var title = gConfigBundle.getFormattedString("modify_title", [gTypeStrs[entry.typeCol]]);
607 if (entry.typeCol == nsIPrefBranch.PREF_BOOL) {
608 var check = { value: entry.valueCol == "false" };
609 if (!entry.valueCol && !Services.prompt.select(window, title, entry.prefCol, 2, [false, true], check))
610 return false;
611 gPrefBranch.setBoolPref(entry.prefCol, check.value);
612 } else {
613 var result = { value: entry.valueCol };
614 var dummy = { value: 0 };
615 if (!Services.prompt.prompt(window, title, entry.prefCol, result, null, dummy))
616 return false;
617 if (entry.typeCol == nsIPrefBranch.PREF_INT) {
618 // | 0 converts to integer or 0; - 0 to float or NaN.
619 // Thus, this check should catch all cases.
620 var val = result.value | 0;
621 if (val != result.value - 0) {
622 var err_title = gConfigBundle.getString("nan_title");
623 var err_text = gConfigBundle.getString("nan_text");
624 Services.prompt.alert(window, err_title, err_text);
625 return false;
626 }
627 gPrefBranch.setIntPref(entry.prefCol, val);
628 } else {
629 var supportsString = Components.classes[nsSupportsString_CONTRACTID].createInstance(nsISupportsString);
630 supportsString.data = result.value;
631 gPrefBranch.setComplexValue(entry.prefCol, nsISupportsString, supportsString);
632 }
633 }
634
635 Services.prefs.savePrefFile(null);
636 return true;
637 }

mercurial