├── profile
└── chrome
│ └── utils
│ ├── chrome.manifest
│ └── aboutconfig
│ ├── config.css
│ ├── config.xhtml
│ └── config.js
├── installdir
├── defaults
│ └── pref
│ │ └── autoconfig.js
└── _autoconfig.cfg
├── README.md
└── aboutcfg.jsm
/profile/chrome/utils/chrome.manifest:
--------------------------------------------------------------------------------
1 | content userchromejs ./
2 |
--------------------------------------------------------------------------------
/installdir/defaults/pref/autoconfig.js:
--------------------------------------------------------------------------------
1 | // Any comment. You must start the file with a single-line comment!
2 |
3 | pref("general.config.filename", "_autoconfig.cfg", locked);
4 | pref("general.config.obscure_value", 0, locked);
5 | pref("general.config.sandbox_enabled", false, locked);
6 |
--------------------------------------------------------------------------------
/installdir/_autoconfig.cfg:
--------------------------------------------------------------------------------
1 | // skip 1st line
2 |
3 | try {
4 | let cmanifest = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties).get('UChrm', Ci.nsIFile);
5 | cmanifest.append('utils');
6 | cmanifest.append('chrome.manifest');
7 |
8 | if(cmanifest.exists()){
9 | Components.manager.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(cmanifest);
10 |
11 | //Cu.import('chrome://userchromejs/content/aboutcfg.jsm');
12 | }
13 |
14 | } catch(ex) {};
15 |
--------------------------------------------------------------------------------
/profile/chrome/utils/aboutconfig/config.css:
--------------------------------------------------------------------------------
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
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | #warningScreen {
6 | font-size: 15px;
7 | padding-top: 0;
8 | padding-bottom: 0;
9 | padding-inline-start: calc(48px + 4.6em);
10 | padding-inline-end: 48px;
11 | }
12 |
13 | .title {
14 | background-image: url("chrome://global/skin/icons/warning.svg");
15 | fill: #fcd100;
16 | }
17 |
18 | #warningTitle {
19 | font-weight: lighter;
20 | line-height: 1.2;
21 | margin: 0;
22 | margin-bottom: .5em;
23 | }
24 |
25 | #warningText {
26 | margin: 1em 0;
27 | }
28 |
29 | #warningButton {
30 | margin-top: 0.6em;
31 | }
32 |
33 | #filterRow {
34 | margin-top: 4px;
35 | margin-inline-start: 4px;
36 | }
37 |
38 | #configTree {
39 | margin-top: 4px;
40 | margin-bottom: 4px;
41 | }
42 |
43 | #configTreeBody::-moz-tree-cell-text(user) {
44 | font-weight: bold;
45 | }
46 |
47 | #configTreeBody::-moz-tree-cell-text(locked) {
48 | font-style: italic;
49 | }
50 |
51 | .deck-selected {
52 | max-width: 100vw;
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## aboutconfig
2 |
3 | The bare minimum to keep the old XUL-based `about:config` version available in Firefox 87+
4 |
5 | Mostly copied from https://github.com/xiaoxiaoflood/firefox-scripts, all credit goes to them.
6 |
7 | All credit goes to [@aminomancer](https://github.com/aminomancer/) for the idea and the code for registering a proper about: page!
8 |
9 |
10 | ## Installation
11 |
12 | 1. download and extract the [master.zip](https://github.com/earthlng/aboutconfig/archive/main.zip)
13 | 2. copy the files/folders from `installdir` into your Firefox installation folder and the `chrome` folder from `profile` into your profile directory
14 | 3. load `about:support` and click the button "Clear startup cache..." (not sure if this is really necessary)
15 | 4. restart Firefox
16 | 5. you can now access the old `about:config` via `chrome://userchromejs/content/aboutconfig/config.xhtml`
17 |
18 | Optionally you can also let the script register an `about:cfg` page. To do that, copy/move `aboutcfg.jsm` into the `profile/chrome/utils/` folder
19 | and uncomment the 2nd-to-last line in the `_autoconfig.cfg` file.
20 | Now you can access the old about:config page at `about:cfg` !
21 |
22 | ### How it works
23 |
24 | - `autoconfig.js` tells Firefox to load the autoconfig file `_autoconfig.cfg`
25 | - `_autoconfig.cfg` tells Firefox to load the `chrome.manifest` from the chrome directory in the profile folder
26 | - `chrome.manifest` registers a `chrome://` namespace `userchromejs`
27 |
28 |
29 | #### original files + modifications made
30 |
31 | Mozilla removed the old `about:config` in https://hg.mozilla.org/mozilla-central/rev/2e2f7a1fd4fa
32 |
33 | see the above link for the original files:
34 |
35 | - toolkit/components/viewconfig/content/config.js
36 | - toolkit/components/viewconfig/content/config.xhtml
37 | - toolkit/locales/en-US/toolkit/about/aboutConfig.ftl
38 | - toolkit/themes/shared/config.css
39 |
40 | The only modifications are:
41 | 1. hardcoding all the localization stuff from aboutConfig.ftl into config.js and config.xhtml
42 | 2. remove the unnecessary telemetry stuff from config.js
43 |
--------------------------------------------------------------------------------
/aboutcfg.jsm:
--------------------------------------------------------------------------------
1 | /*** register an about:cfg page ...
2 | *
3 | * We're not just faking it, this makes it a bona-fide about: page.
4 | * That means you can navigate to it by just typing about:cfg in the urlbar, and the identity icon will show it as a secure system page rather than a local file.
5 | * And about:cfg will even show up on the about:about page!
6 | *
7 | * This technically also makes using the aboutconfig module safer, because it denies the document access to some privileged stuff that it would have with a chrome:// URI.
8 | *
9 | * Optionally edit the config.xhtml file and remove line 13: title="about:config".
10 | * That line sets the tab title to about:config, which (with this script) isn't necessary or desirable since we're changing the URL to about:cfg.
11 | * Without the title attribute, Firefox will automatically set the title to the tab's URL, which is about:cfg.
12 | *
13 | * Big THANKS to @aminomancer ( https://github.com/aminomancer/ )
14 | *
15 | ***/
16 |
17 | (() => {
18 | //const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
19 | let { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
20 | const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
21 |
22 | let dir = Services.dirsvc.get("UChrm", Ci.nsIFile);
23 | let appendFn = (nm) => dir.append(nm);
24 |
25 | ["utils", "aboutconfig", "config.xhtml"].forEach(appendFn);
26 | if (!dir.exists()) return;
27 |
28 | // generate a unique ID on every app launch. protection against the very unlikely possibility that a
29 | // future update adds a component with the same class ID, which would break the script.
30 | function generateFreeCID() {
31 | let uuid = Components.ID(Services.uuid.generateUUID().toString());
32 | // I can't tell whether generateUUID is guaranteed to produce a unique ID, or just a random ID.
33 | // so I add this loop to regenerate it in the extremely unlikely (or potentially impossible)
34 | // event that the UUID is already registered as a CID.
35 | while (registrar.isCIDRegistered(uuid)) {
36 | uuid = Components.ID(Services.uuid.generateUUID().toString());
37 | }
38 | return uuid;
39 | }
40 |
41 | const urlString = 'chrome://userchromejs/content/aboutconfig/config.xhtml';
42 |
43 | function VintageAboutConfig() {}
44 |
45 | VintageAboutConfig.prototype = {
46 | get uri() {
47 | if (!urlString) return null;
48 | return this._uri || (this._uri = Services.io.newURI(urlString));
49 | },
50 | newChannel: function (_uri, loadInfo) {
51 | const ch = Services.io.newChannelFromURIWithLoadInfo(this.uri, loadInfo);
52 | ch.owner = Services.scriptSecurityManager.getSystemPrincipal();
53 | return ch;
54 | },
55 | getURIFlags: function (_uri) {
56 | return Ci.nsIAboutModule.ALLOW_SCRIPT | Ci.nsIAboutModule.IS_SECURE_CHROME_UI;
57 | },
58 | getChromeURI: function (_uri) {
59 | return this.uri;
60 | },
61 | QueryInterface: ChromeUtils.generateQI(["nsIAboutModule"]),
62 | };
63 |
64 | var AboutModuleFactory = {
65 | createInstance(aIID) {
66 | return new VintageAboutConfig().QueryInterface(aIID);
67 | },
68 | QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
69 | };
70 |
71 | registrar.registerFactory(
72 | generateFreeCID(),
73 | 'about:cfg',
74 | '@mozilla.org/network/protocol/about;1?what=cfg',
75 | AboutModuleFactory
76 | );
77 |
78 | })();
79 |
80 | let EXPORTED_SYMBOLS = [];
81 |
--------------------------------------------------------------------------------
/profile/chrome/utils/aboutconfig/config.xhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
73 |
74 |
76 |
77 |
86 |
89 |
90 |
92 |
93 |
95 |
96 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/profile/chrome/utils/aboutconfig/config.js:
--------------------------------------------------------------------------------
1 | // -*- indent-tabs-mode: nil; js-indent-level: 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 | //const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8 |
9 | const nsIPrefLocalizedString = Ci.nsIPrefLocalizedString;
10 | const nsISupportsString = Ci.nsISupportsString;
11 | const nsIPrefBranch = Ci.nsIPrefBranch;
12 | const nsIClipboardHelper = Ci.nsIClipboardHelper;
13 |
14 | const nsClipboardHelper_CONTRACTID = "@mozilla.org/widget/clipboardhelper;1";
15 |
16 | const gPrefBranch = Services.prefs;
17 | const gClipboardHelper = Cc[nsClipboardHelper_CONTRACTID].getService(
18 | nsIClipboardHelper
19 | );
20 |
21 | var gLockProps = ["default", "user", "locked"];
22 | // we get these from a string bundle
23 | var gLockStrs = [];
24 | var gTypeStrs = [];
25 |
26 | const PREF_IS_DEFAULT_VALUE = 0;
27 | const PREF_IS_MODIFIED = 1;
28 | const PREF_IS_LOCKED = 2;
29 |
30 | var gPrefHash = {};
31 | var gPrefArray = [];
32 | var gPrefView = gPrefArray; // share the JS array
33 | var gSortedColumn = "prefCol";
34 | var gSortFunction = null;
35 | var gSortDirection = 1; // 1 is ascending; -1 is descending
36 | var gFilter = null;
37 |
38 | var view = {
39 | get rowCount() {
40 | return gPrefView.length;
41 | },
42 | getCellText(index, col) {
43 | if (!(index in gPrefView)) {
44 | return "";
45 | }
46 |
47 | var value = gPrefView[index][col.id];
48 |
49 | switch (col.id) {
50 | case "lockCol":
51 | return gLockStrs[value];
52 | case "typeCol":
53 | return gTypeStrs[value];
54 | default:
55 | return value;
56 | }
57 | },
58 | getRowProperties(index) {
59 | return "";
60 | },
61 | getCellProperties(index, col) {
62 | if (index in gPrefView) {
63 | return gLockProps[gPrefView[index].lockCol];
64 | }
65 |
66 | return "";
67 | },
68 | getColumnProperties(col) {
69 | return "";
70 | },
71 | treebox: null,
72 | selection: null,
73 | isContainer(index) {
74 | return false;
75 | },
76 | isContainerOpen(index) {
77 | return false;
78 | },
79 | isContainerEmpty(index) {
80 | return false;
81 | },
82 | isSorted() {
83 | return true;
84 | },
85 | canDrop(index, orientation) {
86 | return false;
87 | },
88 | drop(row, orientation) {},
89 | setTree(out) {
90 | this.treebox = out;
91 | },
92 | getParentIndex(rowIndex) {
93 | return -1;
94 | },
95 | hasNextSibling(rowIndex, afterIndex) {
96 | return false;
97 | },
98 | getLevel(index) {
99 | return 1;
100 | },
101 | getImageSrc(row, col) {
102 | return "";
103 | },
104 | toggleOpenState(index) {},
105 | cycleHeader(col) {
106 | var index = this.selection.currentIndex;
107 | if (col.id == gSortedColumn) {
108 | gSortDirection = -gSortDirection;
109 | gPrefArray.reverse();
110 | if (gPrefView != gPrefArray) {
111 | gPrefView.reverse();
112 | }
113 | if (index >= 0) {
114 | index = gPrefView.length - index - 1;
115 | }
116 | } else {
117 | var pref = null;
118 | if (index >= 0) {
119 | pref = gPrefView[index];
120 | }
121 |
122 | var old = document.getElementById(gSortedColumn);
123 | old.removeAttribute("sortDirection");
124 | gPrefArray.sort((gSortFunction = gSortFunctions[col.id]));
125 | if (gPrefView != gPrefArray) {
126 | gPrefView.sort(gSortFunction);
127 | }
128 | gSortedColumn = col.id;
129 | if (pref) {
130 | index = getViewIndexOfPref(pref);
131 | }
132 | }
133 | col.element.setAttribute(
134 | "sortDirection",
135 | gSortDirection > 0 ? "ascending" : "descending"
136 | );
137 | this.treebox.invalidate();
138 | if (index >= 0) {
139 | this.selection.select(index);
140 | this.treebox.ensureRowIsVisible(index);
141 | }
142 | },
143 | selectionChanged() {},
144 | cycleCell(row, col) {},
145 | isEditable(row, col) {
146 | return false;
147 | },
148 | setCellValue(row, col, value) {},
149 | setCellText(row, col, value) {},
150 | isSeparator(index) {
151 | return false;
152 | },
153 | };
154 |
155 | // find the index in gPrefView of a pref object
156 | // or -1 if it does not exist in the filtered view
157 | function getViewIndexOfPref(pref) {
158 | var low = -1,
159 | high = gPrefView.length;
160 | var index = (low + high) >> 1;
161 | while (index > low) {
162 | var mid = gPrefView[index];
163 | if (mid == pref) {
164 | return index;
165 | }
166 | if (gSortFunction(mid, pref) < 0) {
167 | low = index;
168 | } else {
169 | high = index;
170 | }
171 | index = (low + high) >> 1;
172 | }
173 | return -1;
174 | }
175 |
176 | // find the index in gPrefView where a pref object belongs
177 | function getNearestViewIndexOfPref(pref) {
178 | var low = -1,
179 | high = gPrefView.length;
180 | var index = (low + high) >> 1;
181 | while (index > low) {
182 | if (gSortFunction(gPrefView[index], pref) < 0) {
183 | low = index;
184 | } else {
185 | high = index;
186 | }
187 | index = (low + high) >> 1;
188 | }
189 | return high;
190 | }
191 |
192 | // find the index in gPrefArray of a pref object
193 | function getIndexOfPref(pref) {
194 | var low = -1,
195 | high = gPrefArray.length;
196 | var index = (low + high) >> 1;
197 | while (index > low) {
198 | var mid = gPrefArray[index];
199 | if (mid == pref) {
200 | return index;
201 | }
202 | if (gSortFunction(mid, pref) < 0) {
203 | low = index;
204 | } else {
205 | high = index;
206 | }
207 | index = (low + high) >> 1;
208 | }
209 | return index;
210 | }
211 |
212 | function getNearestIndexOfPref(pref) {
213 | var low = -1,
214 | high = gPrefArray.length;
215 | var index = (low + high) >> 1;
216 | while (index > low) {
217 | if (gSortFunction(gPrefArray[index], pref) < 0) {
218 | low = index;
219 | } else {
220 | high = index;
221 | }
222 | index = (low + high) >> 1;
223 | }
224 | return high;
225 | }
226 |
227 | var gPrefListener = {
228 | observe(subject, topic, prefName) {
229 | if (topic != "nsPref:changed") {
230 | return;
231 | }
232 |
233 | var arrayIndex = gPrefArray.length;
234 | var viewIndex = arrayIndex;
235 | var selectedIndex = view.selection.currentIndex;
236 | var pref;
237 | var updateView = false;
238 | var updateArray = false;
239 | var addedRow = false;
240 | if (prefName in gPrefHash) {
241 | pref = gPrefHash[prefName];
242 | viewIndex = getViewIndexOfPref(pref);
243 | arrayIndex = getIndexOfPref(pref);
244 | fetchPref(prefName, arrayIndex);
245 | // fetchPref replaces the existing pref object
246 | pref = gPrefHash[prefName];
247 | if (viewIndex >= 0) {
248 | // Might need to update the filtered view
249 | gPrefView[viewIndex] = gPrefHash[prefName];
250 | view.treebox.invalidateRow(viewIndex);
251 | }
252 | if (gSortedColumn == "lockCol" || gSortedColumn == "valueCol") {
253 | updateArray = true;
254 | gPrefArray.splice(arrayIndex, 1);
255 | if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
256 | updateView = true;
257 | gPrefView.splice(viewIndex, 1);
258 | }
259 | }
260 | } else {
261 | fetchPref(prefName, arrayIndex);
262 | pref = gPrefArray.pop();
263 | updateArray = true;
264 | addedRow = true;
265 | if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
266 | updateView = true;
267 | }
268 | }
269 | if (updateArray) {
270 | // Reinsert in the data array
271 | var newIndex = getNearestIndexOfPref(pref);
272 | gPrefArray.splice(newIndex, 0, pref);
273 |
274 | if (updateView) {
275 | // View is filtered, reinsert in the view separately
276 | newIndex = getNearestViewIndexOfPref(pref);
277 | gPrefView.splice(newIndex, 0, pref);
278 | } else if (gFilter) {
279 | // View is filtered, but nothing to update
280 | return;
281 | }
282 |
283 | if (addedRow) {
284 | view.treebox.rowCountChanged(newIndex, 1);
285 | }
286 |
287 | // Invalidate the changed range in the view
288 | var low = Math.min(viewIndex, newIndex);
289 | var high = Math.max(viewIndex, newIndex);
290 | view.treebox.invalidateRange(low, high);
291 |
292 | if (selectedIndex == viewIndex) {
293 | selectedIndex = newIndex;
294 | } else if (selectedIndex >= low && selectedIndex <= high) {
295 | selectedIndex += newIndex > viewIndex ? -1 : 1;
296 | }
297 | if (selectedIndex >= 0) {
298 | view.selection.select(selectedIndex);
299 | if (selectedIndex == newIndex) {
300 | view.treebox.ensureRowIsVisible(selectedIndex);
301 | }
302 | }
303 | }
304 | },
305 | };
306 |
307 | function prefObject(prefName, prefIndex) {
308 | this.prefCol = prefName;
309 | }
310 |
311 | prefObject.prototype = {
312 | lockCol: PREF_IS_DEFAULT_VALUE,
313 | typeCol: nsIPrefBranch.PREF_STRING,
314 | valueCol: "",
315 | };
316 |
317 | function fetchPref(prefName, prefIndex) {
318 | var pref = new prefObject(prefName);
319 |
320 | gPrefHash[prefName] = pref;
321 | gPrefArray[prefIndex] = pref;
322 |
323 | if (gPrefBranch.prefIsLocked(prefName)) {
324 | pref.lockCol = PREF_IS_LOCKED;
325 | } else if (gPrefBranch.prefHasUserValue(prefName)) {
326 | pref.lockCol = PREF_IS_MODIFIED;
327 | }
328 |
329 | try {
330 | switch (gPrefBranch.getPrefType(prefName)) {
331 | case gPrefBranch.PREF_BOOL:
332 | pref.typeCol = gPrefBranch.PREF_BOOL;
333 | // convert to a string
334 | pref.valueCol = gPrefBranch.getBoolPref(prefName).toString();
335 | break;
336 | case gPrefBranch.PREF_INT:
337 | pref.typeCol = gPrefBranch.PREF_INT;
338 | // convert to a string
339 | pref.valueCol = gPrefBranch.getIntPref(prefName).toString();
340 | break;
341 | default:
342 | case gPrefBranch.PREF_STRING:
343 | pref.valueCol = gPrefBranch.getStringPref(prefName);
344 | // Try in case it's a localized string (will throw an exception if not)
345 | if (
346 | pref.lockCol == PREF_IS_DEFAULT_VALUE &&
347 | /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.valueCol)
348 | ) {
349 | pref.valueCol = gPrefBranch.getComplexValue(
350 | prefName,
351 | nsIPrefLocalizedString
352 | ).data;
353 | }
354 | break;
355 | }
356 | } catch (e) {
357 | // Also catch obscure cases in which you can't tell in advance
358 | // that the pref exists but has no user or default value...
359 | }
360 | }
361 |
362 | async function onConfigLoad() {
363 | let configContext = document.getElementById("configContext");
364 | configContext.addEventListener("popupshowing", function(event) {
365 | if (event.target == this) {
366 | updateContextMenu();
367 | }
368 | });
369 |
370 | let commandListeners = {
371 | toggleSelected: ModifySelected,
372 | modifySelected: ModifySelected,
373 | copyPref,
374 | copyName,
375 | copyValue,
376 | resetSelected: ResetSelected,
377 | };
378 |
379 | configContext.addEventListener("command", e => {
380 | if (e.target.id in commandListeners) {
381 | commandListeners[e.target.id]();
382 | }
383 | });
384 |
385 | let configString = document.getElementById("configString");
386 | configString.addEventListener("command", function() {
387 | NewPref(nsIPrefBranch.PREF_STRING);
388 | });
389 |
390 | let configInt = document.getElementById("configInt");
391 | configInt.addEventListener("command", function() {
392 | NewPref(nsIPrefBranch.PREF_INT);
393 | });
394 |
395 | let configBool = document.getElementById("configBool");
396 | configBool.addEventListener("command", function() {
397 | NewPref(nsIPrefBranch.PREF_BOOL);
398 | });
399 |
400 | let keyVKReturn = document.getElementById("keyVKReturn");
401 | keyVKReturn.addEventListener("command", ModifySelected);
402 |
403 | let textBox = document.getElementById("textbox");
404 | textBox.addEventListener("command", FilterPrefs);
405 |
406 | let configFocuSearch = document.getElementById("configFocuSearch");
407 | configFocuSearch.addEventListener("command", function() {
408 | textBox.focus();
409 | });
410 |
411 | let configFocuSearch2 = document.getElementById("configFocuSearch2");
412 | configFocuSearch2.addEventListener("command", function() {
413 | textBox.focus();
414 | });
415 |
416 | let warningButton = document.getElementById("warningButton");
417 | warningButton.addEventListener("command", ShowPrefs);
418 |
419 | let configTree = document.getElementById("configTree");
420 | configTree.addEventListener("select", function() {
421 | window.updateCommands("select");
422 | });
423 |
424 | let configTreeBody = document.getElementById("configTreeBody");
425 | configTreeBody.addEventListener("dblclick", function(event) {
426 | if (event.button == 0) {
427 | ModifySelected();
428 | }
429 | });
430 |
431 | gLockStrs[PREF_IS_DEFAULT_VALUE] = 'default';
432 | gLockStrs[PREF_IS_MODIFIED] = 'modified';
433 | gLockStrs[PREF_IS_LOCKED] = 'locked';
434 | gTypeStrs[nsIPrefBranch.PREF_STRING] = 'string';
435 | gTypeStrs[nsIPrefBranch.PREF_INT] = 'integer';
436 | gTypeStrs[nsIPrefBranch.PREF_BOOL] = 'boolean';
437 |
438 | var showWarning = gPrefBranch.getBoolPref("general.warnOnAboutConfig");
439 |
440 | if (showWarning) {
441 | document.getElementById("warningButton").focus();
442 | } else {
443 | ShowPrefs();
444 | }
445 | }
446 |
447 | // Unhide the warning message
448 | function ShowPrefs() {
449 | gPrefBranch.getChildList("").forEach(fetchPref);
450 |
451 | var descending = document.getElementsByAttribute(
452 | "sortDirection",
453 | "descending"
454 | );
455 | if (descending.item(0)) {
456 | gSortedColumn = descending[0].id;
457 | gSortDirection = -1;
458 | } else {
459 | var ascending = document.getElementsByAttribute(
460 | "sortDirection",
461 | "ascending"
462 | );
463 | if (ascending.item(0)) {
464 | gSortedColumn = ascending[0].id;
465 | } else {
466 | document
467 | .getElementById(gSortedColumn)
468 | .setAttribute("sortDirection", "ascending");
469 | }
470 | }
471 | gSortFunction = gSortFunctions[gSortedColumn];
472 | gPrefArray.sort(gSortFunction);
473 |
474 | gPrefBranch.addObserver("", gPrefListener);
475 |
476 | var configTree = document.getElementById("configTree");
477 | configTree.view = view;
478 | configTree.controllers.insertControllerAt(0, configController);
479 |
480 | //document.getElementById("configDeck").setAttribute("selectedIndex", 1); // FF106-
481 | //document.getElementById("configDeck").updateSelectedIndex(1); // FF107+
482 | document.getElementById("configDeck").selectedIndex = 1; // FF107+ Release & ESR102.x
483 |
484 | document.getElementById("configTreeKeyset").removeAttribute("disabled");
485 | if (!document.getElementById("showWarningNextTime").checked) {
486 | gPrefBranch.setBoolPref("general.warnOnAboutConfig", false);
487 | }
488 |
489 | // Process about:config?filter=
490 | var textbox = document.getElementById("textbox");
491 | // About URIs don't support query params, so do this manually
492 | var loc = document.location.href;
493 | var matches = /[?&]filter\=([^&]+)/i.exec(loc);
494 | if (matches) {
495 | textbox.value = decodeURIComponent(matches[1]);
496 | }
497 |
498 | // Even if we did not set the filter string via the URL query,
499 | // textbox might have been set via some other mechanism
500 | if (textbox.value) {
501 | FilterPrefs();
502 | }
503 | textbox.focus();
504 | }
505 |
506 | function onConfigUnload() {
507 | if (
508 | document.getElementById("configDeck").getAttribute("selectedIndex") == 1
509 | ) {
510 | gPrefBranch.removeObserver("", gPrefListener);
511 | var configTree = document.getElementById("configTree");
512 | configTree.view = null;
513 | configTree.controllers.removeController(configController);
514 | }
515 | }
516 |
517 | function FilterPrefs() {
518 | if (
519 | document.getElementById("configDeck").getAttribute("selectedIndex") != 1
520 | ) {
521 | return;
522 | }
523 |
524 | var substring = document.getElementById("textbox").value;
525 | // Check for "/regex/[i]"
526 | if (substring.charAt(0) == "/") {
527 | var r = substring.match(/^\/(.*)\/(i?)$/);
528 | try {
529 | gFilter = RegExp(r[1], r[2]);
530 | } catch (e) {
531 | return; // Do nothing on incomplete or bad RegExp
532 | }
533 | } else if (substring) {
534 | gFilter = RegExp(
535 | substring
536 | .replace(/([^* \w])/g, "\\$1")
537 | .replace(/^\*+/, "")
538 | .replace(/\*+/g, ".*"),
539 | "i"
540 | );
541 | } else {
542 | gFilter = null;
543 | }
544 |
545 | var prefCol =
546 | view.selection && view.selection.currentIndex < 0
547 | ? null
548 | : gPrefView[view.selection.currentIndex].prefCol;
549 | var oldlen = gPrefView.length;
550 | gPrefView = gPrefArray;
551 | if (gFilter) {
552 | gPrefView = [];
553 | for (var i = 0; i < gPrefArray.length; ++i) {
554 | if (gFilter.test(gPrefArray[i].prefCol + ";" + gPrefArray[i].valueCol)) {
555 | gPrefView.push(gPrefArray[i]);
556 | }
557 | }
558 | }
559 | view.treebox.invalidate();
560 | view.treebox.rowCountChanged(oldlen, gPrefView.length - oldlen);
561 | gotoPref(prefCol);
562 | }
563 |
564 | function prefColSortFunction(x, y) {
565 | if (x.prefCol > y.prefCol) {
566 | return gSortDirection;
567 | }
568 | if (x.prefCol < y.prefCol) {
569 | return -gSortDirection;
570 | }
571 | return 0;
572 | }
573 |
574 | function lockColSortFunction(x, y) {
575 | if (x.lockCol != y.lockCol) {
576 | return gSortDirection * (y.lockCol - x.lockCol);
577 | }
578 | return prefColSortFunction(x, y);
579 | }
580 |
581 | function typeColSortFunction(x, y) {
582 | if (x.typeCol != y.typeCol) {
583 | return gSortDirection * (y.typeCol - x.typeCol);
584 | }
585 | return prefColSortFunction(x, y);
586 | }
587 |
588 | function valueColSortFunction(x, y) {
589 | if (x.valueCol > y.valueCol) {
590 | return gSortDirection;
591 | }
592 | if (x.valueCol < y.valueCol) {
593 | return -gSortDirection;
594 | }
595 | return prefColSortFunction(x, y);
596 | }
597 |
598 | const gSortFunctions = {
599 | prefCol: prefColSortFunction,
600 | lockCol: lockColSortFunction,
601 | typeCol: typeColSortFunction,
602 | valueCol: valueColSortFunction,
603 | };
604 |
605 | const gCategoryLabelForSortColumn = {
606 | prefCol: "SortByName",
607 | lockCol: "SortByStatus",
608 | typeCol: "SortByType",
609 | valueCol: "SortByValue",
610 | };
611 |
612 | const configController = {
613 | supportsCommand: function supportsCommand(command) {
614 | return command == "cmd_copy";
615 | },
616 | isCommandEnabled: function isCommandEnabled(command) {
617 | return view.selection && view.selection.currentIndex >= 0;
618 | },
619 | doCommand: function doCommand(command) {
620 | copyPref();
621 | },
622 | onEvent: function onEvent(event) {},
623 | };
624 |
625 | function updateContextMenu() {
626 | var lockCol = PREF_IS_LOCKED;
627 | var typeCol = nsIPrefBranch.PREF_STRING;
628 | var valueCol = "";
629 | var copyDisabled = true;
630 | var prefSelected = view.selection.currentIndex >= 0;
631 |
632 | if (prefSelected) {
633 | var prefRow = gPrefView[view.selection.currentIndex];
634 | lockCol = prefRow.lockCol;
635 | typeCol = prefRow.typeCol;
636 | valueCol = prefRow.valueCol;
637 | copyDisabled = false;
638 | }
639 |
640 | var copyPref = document.getElementById("copyPref");
641 | copyPref.setAttribute("disabled", copyDisabled);
642 |
643 | var copyName = document.getElementById("copyName");
644 | copyName.setAttribute("disabled", copyDisabled);
645 |
646 | var copyValue = document.getElementById("copyValue");
647 | copyValue.setAttribute("disabled", copyDisabled);
648 |
649 | var resetSelected = document.getElementById("resetSelected");
650 | resetSelected.setAttribute("disabled", lockCol != PREF_IS_MODIFIED);
651 |
652 | var canToggle = typeCol == nsIPrefBranch.PREF_BOOL && valueCol != "";
653 | // indicates that a pref is locked or no pref is selected at all
654 | var isLocked = lockCol == PREF_IS_LOCKED;
655 |
656 | var modifySelected = document.getElementById("modifySelected");
657 | modifySelected.setAttribute("disabled", isLocked);
658 | modifySelected.hidden = canToggle;
659 |
660 | var toggleSelected = document.getElementById("toggleSelected");
661 | toggleSelected.setAttribute("disabled", isLocked);
662 | toggleSelected.hidden = !canToggle;
663 | }
664 |
665 | function copyPref() {
666 | var pref = gPrefView[view.selection.currentIndex];
667 | gClipboardHelper.copyString(pref.prefCol + ";" + pref.valueCol);
668 | }
669 |
670 | function copyName() {
671 | gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].prefCol);
672 | }
673 |
674 | function copyValue() {
675 | gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].valueCol);
676 | }
677 |
678 | function ModifySelected() {
679 | if (view.selection.currentIndex >= 0) {
680 | ModifyPref(gPrefView[view.selection.currentIndex]);
681 | }
682 | }
683 |
684 | function ResetSelected() {
685 | var entry = gPrefView[view.selection.currentIndex];
686 | gPrefBranch.clearUserPref(entry.prefCol);
687 | }
688 |
689 | async function NewPref(type) {
690 | var result = { value: "" };
691 | var dummy = { value: 0 };
692 |
693 | let [newTitle, newPrompt] = [`New ${gTypeStrs[type]} value`, 'Enter the preference name'];
694 |
695 | if (
696 | Services.prompt.prompt(window, newTitle, newPrompt, result, null, dummy)
697 | ) {
698 | result.value = result.value.trim();
699 | if (!result.value) {
700 | return;
701 | }
702 |
703 | var pref;
704 | if (result.value in gPrefHash) {
705 | pref = gPrefHash[result.value];
706 | } else {
707 | pref = {
708 | prefCol: result.value,
709 | lockCol: PREF_IS_DEFAULT_VALUE,
710 | typeCol: type,
711 | valueCol: "",
712 | };
713 | }
714 | if (ModifyPref(pref)) {
715 | setTimeout(gotoPref, 0, result.value);
716 | }
717 | }
718 | }
719 |
720 | function gotoPref(pref) {
721 | // make sure the pref exists and is displayed in the current view
722 | var index = pref in gPrefHash ? getViewIndexOfPref(gPrefHash[pref]) : -1;
723 | if (index >= 0) {
724 | view.selection.select(index);
725 | view.treebox.ensureRowIsVisible(index);
726 | } else {
727 | view.selection.clearSelection();
728 | view.selection.currentIndex = -1;
729 | }
730 | }
731 |
732 | async function ModifyPref(entry) {
733 | if (entry.lockCol == PREF_IS_LOCKED) {
734 | return false;
735 | }
736 |
737 | let [title] = [`Enter ${gTypeStrs[entry.typeCol]} value`];
738 |
739 | if (entry.typeCol == nsIPrefBranch.PREF_BOOL) {
740 | var check = { value: entry.valueCol == "false" };
741 | if (
742 | !entry.valueCol &&
743 | !Services.prompt.select(
744 | window,
745 | title,
746 | entry.prefCol,
747 | [false, true],
748 | check
749 | )
750 | ) {
751 | return false;
752 | }
753 | gPrefBranch.setBoolPref(entry.prefCol, check.value);
754 | } else {
755 | var result = { value: entry.valueCol };
756 | var dummy = { value: 0 };
757 | if (
758 | !Services.prompt.prompt(window, title, entry.prefCol, result, null, dummy)
759 | ) {
760 | return false;
761 | }
762 | if (entry.typeCol == nsIPrefBranch.PREF_INT) {
763 | // | 0 converts to integer or 0; - 0 to float or NaN.
764 | // Thus, this check should catch all cases.
765 | var val = result.value | 0;
766 | if (val != result.value - 0) {
767 | const [err_title, err_text] = ['Invalid value', 'The text you entered is not a number.'];
768 |
769 | Services.prompt.alert(window, err_title, err_text);
770 | return false;
771 | }
772 | gPrefBranch.setIntPref(entry.prefCol, val);
773 | } else {
774 | gPrefBranch.setStringPref(entry.prefCol, result.value);
775 | }
776 | }
777 |
778 | Services.prefs.savePrefFile(null);
779 | return true;
780 | }
781 |
782 | window.onload = onConfigLoad;
783 | window.onunload = onConfigUnload;
784 |
--------------------------------------------------------------------------------