<!DOCTYPE html> <html xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi, shrink-to-fit=no"> <!-- <style>* {-webkit-text-size-adjust: none}</style> --> <!-- ************************************* Interactive view, compare, and more for Firefox user.js Name : userjs-tool.html Project : https://github.com/icpantsparti/firefox-user.js-tool Version : 2021.03.11 (alpha/experimental) File : https://raw.githubusercontent.com/icpantsparti/firefox-user.js-tool/master/userjs-tool.html On-line : https://icpantsparti.github.io/firefox-user.js-tool/userjs-tool.html License (MIT): https://raw.githubusercontent.com/icpantsparti/firefox-user.js-tool/master/LICENSE Disclaimer : Use with care at your own risk, and verify any results Open on-line or save for off-line use. Visit the project page for updates/issues/etc. Other sources: shown in the code (embedded libraries/functions) ************************************* --> <title>userjs-tool.html</title> <script type="text/javascript"> var date_time_stamp; // keep track of the section selected to help overcome focus loss var section_focus, return_to_panel; </script> <style type="text/css"> /* similar color style to the static HTML that arkenfox/user.js once produced */ /* main blocks */ body { font-family: Consolas,"Courier New","Liberation Mono",monospace; font-size: 100%; /* white-space: normal|nowrap|pre|pre-line|pre-wrap|initial|inherit; */ /* word-wrap: normal|break-word|initial|inherit; */ /* overflow: visible|hidden|scroll|auto|initial|inherit; */ /* float: none|left|right|initial|inherit; */ } /* actions/groups/links/functions/help panels */ .panels { display: none; position: fixed; top: 5em; width: 85vw; min-width: 85vw; max-width: 85vw; height: 75vh; min-height: 0vh; max-height: 75vh; padding: 20px; overflow: auto; white-space: normal; /* theme will override the following */ background-color: #FFFFFF; /*White*/ box-shadow: 3px 3px 4px #000000; /*Black*/ border: 1px solid #000000; /*Black*/ border-width: 1px 1px 0px 1px; } #view_area, #groups_panel { white-space: nowrap; } #diffstr_area { white-space: pre; } /* help_panel */ #help_panel { display: block; } #help_panel_nojs_div { display: block; padding: 20px; margin: 0px 0px 20px 0px; overflow: auto; white-space: normal; width: 90%; /* default borders */ box-shadow: 3px 3px 4px #000000; /*Black*/ border: 1px solid #000000; /*Black*/ border-width: 1px 1px 0px 1px; } .indentdiv { margin: 0px 0px 0px 20px; } /* all buttons and selects */ .controls { font: inherit; padding: 5px; float: left; overflow: hidden; white-space: pre; -moz-appearance: none; text-align: center; } /* main buttons at the top */ #top_buttons_bar { display: inline-block; position: fixed; background-color: transparent; } .top_buttons { display: inline-block; width: 5em; max-width: 5em; height: 2.6em; padding: 0; } .close_panel_x { font-weight: bold; font-size: 300%; cursor: pointer; position: fixed; /*absolute;*/ top: 5vh; left: 80.5vw; transform: translate(100%, 100%); } /* within actions panel */ #div_1_2, #div_3_4 { width: 100%; overflow: hidden; } /* actions tables */ .td1_actions { text-align: left; vertical-align: top; width: 10%; padding: 0px 10px 0px 0px; } .td2_actions { text-align: left; vertical-align: top; width: 90%; padding: 0px 10px 0px 0px; } /* input boxes */ textarea { font: inherit; background-color: inherit; color: inherit; width: 100%; min-width: 100%; max-width: 100%; height: 11.5em; min-height: 11.5em; max-height: 50vh; white-space: pre; resize: vertical; margin: 0px 0px 0px 0px; } /* buttons above input boxes */ #actions_buttons_bar { margin: 0 0 4em 0; } #div_1_template_button, #div_2_overrides_button, #div_3_userjs_button, #div_4_other_button, #all_button { padding: 0px; height: 3em; width: 6.5em; } #all_button { width: 3em; } /* buttons left of input boxes */ #box_1_template_ro, #box_2_overrides_ro, #box_3_userjs_ro, #box_4_other_ro { display: inline-block; cursor: cell; } #div_1_template .controls, #div_2_overrides .controls, #div_3_userjs .controls, #div_4_other .controls, #select_functions_button { height: 2.3em; width: 7.7em; } #load_template_input, #load_overrides_input, #load_userjs_input, #load_other_input { height: 1.9em !important; width: 7em !important; } /* buttons under input boxes */ #actions_buttons_div { white-space: normal; padding: 15px 0px; width: 100%; float: left; } #actions_buttons_div .controls { height: 2.4em; width: 10em; float: none; } #endcollect_button, #endcollect_br { display: none; } #functions_panel_textarea { width: 99%; min-width: 99%; max-width: 99%; height: 70%; min-height: 11.5em; max-height: 100%; } /* links panel */ .table_links { width: 95%; border-collapse: collapse; } .td1_links { border: 1px solid; text-align: left; vertical-align: top; vertical-align: center; padding: 10px; width: 80%; } .td2_links { border: 1px solid; text-align: left; vertical-align: top; vertical-align: center; padding: 10px; width: 20%; } .td3_links { border-left: 0px; border-right: 0px; } /* compare */ /* compare stats table */ #table_stats { width: 95%; border-spacing: 1px; } .td1_stats_count, .td2_stats_count { width: 10%; text-align: right; padding: 0px 20px 0px 0px; } .td3a_stats_name { width:80%; } .td3b_stats_name { width:90%; } /* compare table */ #table_comp { table-layout: fixed; width: 95%; border-spacing: 1px; white-space: normal; } .td_comp { border: 1px dotted; vertical-align: top; word-break: break-all; padding: 0px 10px 0px 10px; } .td1a_comp_name { width: 30%; } .td1b_comp_name { width: 40%; } .td2td4_comp_state { width: 2em; } .td3td5_comp_value { width: auto; } .td3td5_b_comp_value { width: 100%; } .td_comp_dashed { border-top: 1px dashed; } .td_comp_diff { border: solid 3px; } /* #table_comp tr:hover { outline: 3px dashed #FF0000; } */ /* tableview */ #table_tview { table-layout: fixed; width: 100%; border-spacing: 1px; white-space: normal; border-collapse: collapse; } .tr_tview_heading { border: solid 3px; white-space: pre; border-left: 0px; border-right: 0px; } /* https://css-tricks.com/hash-tag-links-padding/ */ .anchor { margin-top: -165px; padding-bottom: 165px; display: block; } .anav { display: inline-block; cursor: pointer; } .tr_tview_inactive { background-color: #373737; } .td_tview_all { border: 1px dotted; border-left: 0px; border-right: 0px; vertical-align: top; word-break: break-all; padding: 10px; } /* .tr_tview_pref:hover { outline: 3px dashed #FF0000; } */ .td_tview_id { width: 3.5em; } .td_tview_name { width: 20%; } .td_tview_value { width: 10%; } .td_tview_desc { width: auto; } .td_tview_info { width: 5em; } /* buttons bar and navigation (on compare and tableview) */ /* https://www.w3schools.com/howto/howto_js_rangeslider.asp */ #compare_buttons_bar, #tableview_buttons_bar { width: auto; position: fixed; top: 5em; /*background-color: inherit;*/ } #compare_buttons_bar .controls, #tableview_buttons_bar .controls { height: 2em; /*min-width: 6.5em;*/ /*width: auto;*/ } #compare_buttons_bar .controls { padding: 0 0.2em 0 0.2em; min-width: 4em; } #tableview_buttons_bar .controls { padding: 0; } #viewer_slider { -webkit-appearance: none; width: calc(100% - 22em); max-width: 75vw; height: 2.2em; background: #d3d3d3; outline: none; opacity: 0.7; -webkit-transition: .2s; transition: opacity .2s; float: left; } #viewer_slider:hover { opacity: 1; } #viewer_slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 2.1em; height: 2.1em; background: #4CAF50; cursor: pointer; } #viewer_slider::-moz-range-thumb { width: 2.1em; height: 2.1em; background: #4CAF50; cursor: pointer; } /* within view area */ #collect_mode_pad { display: none; white-space: normal; } .heading_buttons { padding-left: 20px; width: 86%; text-align: left; float: none; } .content { display: none; margin: auto; padding: 20px 0px 0px 20px; white-space: pre; } .hidden { display: none; } a { text-decoration: none; } a:hover, button:hover, .pref, .false, .true, .integer, .string, .warn, .hid, .ref { font-weight: bold; } .setup { font-weight: bold; text-decoration: underline; } details > summary { font-weight: bold; } details > summary:hover { cursor: pointer; } details[open] > summary { text-decoration: underline; } del { font-weight: bold; } ins { text-decoration: none; } .jump { color: #FF0000 !important; /*Red*/ background-color: #FFFF00 !important; /*Yellow*/ } /* ************************************* */ /* color themes */ /* ************************************* */ /* default theme has same values as dark theme */ /* use an optional "userjs-tool-themes.css" file to override these */ /* color codes: https://www.w3schools.com/colors/colors_groups.asp */ /* default theme */ .body_default, .view_area_default, .panels_default { background-color: #262626; /*(black)*/ color: #b3b3b3; /*(gray)*/ } .controls_default { background-color: #555555; /*DimGray*/ color: #C0C0C0; /*Silver*/ } .controls_default:focus { outline: 2px auto #FF0000; /*Red*/ } .controls_default:hover { background-color: #DAA520; /*GoldenRod*/ color: #000000; /*Black*/ } .borders_default { box-shadow: 3px 3px 4px #FFFFFF; /*White*/ border: 1px solid #A9A9A9; /*DarkGray*/ border-width: 1px 1px 0px 1px; } .view_area_default .pref { color: #FFFFFF; /*White*/ } .body_default a { color: #ffbf80; /*(brown)*/ } .view_area_default .false { color: #FF6347; /*Tomato*/ } .view_area_default .true { color: #00FF00; /*Lime*/ } .view_area_default .integer { color: #FFFF00; /*Yellow*/ } .view_area_default .string { color: #DDA0DD; /*Plum*/ } .view_area_default .http { color: #99ccff; /*(blue)*/ } .view_area_default .warn { background-color: #FF3333; /*(Red)*/ color: #FFFFFF; /*White*/ } .view_area_default .hid { color: #C6F3C6; /*(Green)*/ } .view_area_default .ref { color: #98E498; /*(Green)*/ } /* dark theme */ .body_dark, .view_area_dark, .panels_dark { background-color: #262626; /*(black)*/ color: #b3b3b3; /*(gray)*/ } .controls_dark { background-color: #555555; /*DimGray*/ color: #C0C0C0; /*Silver*/ } .controls_dark:focus { outline: 2px auto #FF0000; /*Red*/ } .controls_dark:hover { background-color: #DAA520; /*GoldenRod*/ color: #000000; /*Black*/ } .borders_dark { box-shadow: 3px 3px 4px #FFFFFF; /*White*/ border: 1px solid #A9A9A9; /*DarkGray*/ border-width: 1px 1px 0px 1px; } .view_area_dark .pref { color: #FFFFFF; /*White*/ } .body_dark a { color: #ffbf80; /*(brown)*/ } .view_area_dark .false { color: #FF6347; /*Tomato*/ } .view_area_dark .true { color: #00FF00; /*Lime*/ } .view_area_dark .integer { color: #FFFF00; /*Yellow*/ } .view_area_dark .string { color: #DDA0DD; /*Plum*/ } .view_area_dark .http { color: #99ccff; /*(blue)*/ } .view_area_dark .warn { background-color: #FF3333; /*(Red)*/ color: #FFFFFF; /*White*/ } .view_area_dark .hid { color: #C6F3C6; /*(Green)*/ } .view_area_dark .ref { color: #98E498; /*(Green)*/ } /* light theme */ .body_light, .view_area_light, .panels_light { background-color: #FFFFFF; /*White*/ color: #808080; /*Gray*/ } .controls_light { background-color: #C0C0C0; /*Silver*/ color: #000000; /*Black*/ } .controls_light:focus { outline: 2px auto #FF0000; /*Red*/ } .controls_light:hover { background-color: #DAA520; /*GoldenRod*/ color: #000000; /*Black*/ } .borders_light { box-shadow: 3px 3px 4px #000000; /*Black*/ border: 1px solid #000000; /*Black*/ border-width: 1px 1px 0px 1px; } .view_area_light .pref { color: #000000; /*Black*/ } .body_light a { color: #D2691E; /*Chocolate*/ } .view_area_light .false { color: #FF6347; /*Tomato*/ } .view_area_light .true { color: #32CD32; /*LimeGreen*/ } .view_area_light .integer { color: #9932CC; /*DarkOrchid*/ } .view_area_light .string { color: #DDA0DD; /*Plum*/ } .view_area_light .http { color: #1982D1; /*(Blue)*/ } .view_area_light .warn { background-color: #FF3333; /*(Red)*/ color: #FFFFFF; /*White*/ } .view_area_light .hid { color: #598033; /*(Green)*/ } .view_area_light .ref { color: #A53C00; /*(Brown)*/ } /* mono theme */ .body_mono, .view_area_mono, .panels_mono { background-color: #FFFFFF; /*White*/ color: #000000; /*Black*/ } .controls_mono { background-color: #FFFFFF; /*White*/ color: #000000; /*Black*/ } .controls_mono:focus { outline: 2px auto #FF0000; /*Red*/ } .controls_mono:hover { background-color: #000000; /*Black*/ color: #FFFFFF; /*White*/ } .borders_mono { box-shadow: 3px 3px 4px #000000; /*Black*/ border: 1px solid #000000; /*Black*/ border-width: 1px 1px 0px 1px; } .view_area_mono .pref { color: #000000; /*Black*/ } .body_mono a { color: #696969; /*DimGray*/ } .view_area_mono .false { color: #000000; /*Black*/ } .view_area_mono .true { color: #000000; /*Black*/ } .view_area_mono .integer { color: #000000; /*Black*/ } .view_area_mono .string { color: #000000; /*Black*/ } .view_area_mono .http { color: #696969; /*DimGray*/ } .view_area_mono .warn { background-color: #000000; /*Black*/ color: #FFFFFF; /*White*/ } .view_area_mono .hid { color: #000000; /*Black*/ } .view_area_mono .ref { color: #000000; /*Black*/ } /* inverse theme */ .body_inverse, .view_area_inverse, .panels_inverse { background-color: #000000; /*Black*/ color: #FFFFFF; /*White*/ } .controls_inverse { background-color: #000000; /*Black*/ color: #FFFFFF; /*White*/ } .controls_inverse:focus { outline: 2px auto #FF0000; /*Red*/ } .controls_inverse:hover { background-color: #FFFFFF; /*White*/ color: #000000; /*Black*/ } .borders_inverse { box-shadow: 3px 3px 4px #FFFFFF; /*White*/ border: 1px solid #FFFFFF; /*White*/ border-width: 1px 1px 0px 1px; } .view_area_inverse .pref { color: #FFFFFF; /*White*/ } .body_inverse a { color: #D3D3D3; /*LightGray*/ } .view_area_inverse .false { color: #FFFFFF; /*White*/ } .view_area_inverse .true { color: #FFFFFF; /*White*/ } .view_area_inverse .integer { color: #FFFFFF; /*White*/ } .view_area_inverse .string { color: #FFFFFF; /*White*/ } .view_area_inverse .http { color: #D3D3D3; /*LightGray*/ } .view_area_inverse .warn { background-color: #FFFFFF; /*White*/ color: #000000; /*Black*/ } .view_area_inverse .hid { color: #FFFFFF; /*White*/ } .view_area_inverse .ref { color: #FFFFFF; /*White*/ } </style> <!-- ************************************* userjs-tool-themes.css (optional file) (to override default theme with users color choice) ************************************* --> <link rel="stylesheet" href="userjs-tool-themes.css" /> <!-- ************************************* embedded JavaScript library (used when saving textarea contents to a file) name: download.js version: 4.2 author: dandavis URL: https://github.com/rndme/download license: Creative Commons Attribution 4.0 International License license: http://creativecommons.org/licenses/by/4.0/ ************************************* --> <script type="text/javascript"> //download.js v4.2, by dandavis; 2008-2016. [CCBY2] see http://danml.com/download.html for tests/usage // v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime // v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs // v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling. // v4 adds AMD/UMD, commonJS, and plain browser support // v4.1 adds url download capability via solo URL argument (same domain/CORS only) // v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors // https://github.com/rndme/download (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) root.download = factory(); } }(this, function () { return function download(data, strFileName, strMimeType) { var self = window, // this script is only for browsers anyway... defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads mimeType = strMimeType || defaultMime, payload = data, url = !strFileName && !strMimeType && payload, anchor = document.createElement("a"), toString = function(a){return String(a);}, myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString), fileName = strFileName || "download", blob, reader; myBlob= myBlob.call ? myBlob.bind(self) : Blob ; if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback payload=[payload, mimeType]; mimeType=payload[0]; payload=payload[1]; } if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument fileName = url.split("/").pop().split("?")[0]; anchor.href = url; // assign href prop to temp anchor if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path: var ajax=new XMLHttpRequest(); ajax.open( "GET", url, true); ajax.responseType = 'blob'; ajax.onload= function(e){ download(e.target.response, fileName, defaultMime); }; setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return: return ajax; } // end if valid url? } // end if url? //go ahead and download dataURLs right away if(/^data\:[\w+\-]+\/[\w+\-]+[,;]/.test(payload)){ if(payload.length > (1024*1024*1.999) && myBlob !== toString ){ payload=dataUrlToBlob(payload); mimeType=payload.type || defaultMime; }else{ return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs: navigator.msSaveBlob(dataUrlToBlob(payload), fileName) : saver(payload) ; // everyone else can save dataURLs un-processed } }//end if dataURL passed? blob = payload instanceof myBlob ? payload : new myBlob([payload], {type: mimeType}) ; function dataUrlToBlob(strUrl) { var parts= strUrl.split(/[:;,]/), type= parts[1], decoder= parts[2] == "base64" ? atob : decodeURIComponent, binData= decoder( parts.pop() ), mx= binData.length, i= 0, uiArr= new Uint8Array(mx); for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i); return new myBlob([uiArr], {type: type}); } function saver(url, winMode){ if ('download' in anchor) { //html5 A[download] anchor.href = url; anchor.setAttribute("download", fileName); anchor.className = "download-js-link"; anchor.innerHTML = "downloading..."; anchor.style.display = "none"; document.body.appendChild(anchor); setTimeout(function() { anchor.click(); document.body.removeChild(anchor); if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );} }, 66); return true; } // handle non-a[download] safari as best we can: if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) { url=url.replace(/^data:([\w\/\-\+]+)/, defaultMime); if(!window.open(url)){ // popup blocked, offer direct download: if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; } } return true; } //do iframe dataURL download (old ch+FF): var f = document.createElement("iframe"); document.body.appendChild(f); if(!winMode){ // force a mime that will download: url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime); } f.src=url; setTimeout(function(){ document.body.removeChild(f); }, 333); }//end saver if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL) return navigator.msSaveBlob(blob, fileName); } if(self.URL){ // simple fast and modern way using Blob and URL: saver(self.URL.createObjectURL(blob), true); }else{ // handle non-Blob()+non-URL browsers: if(typeof blob === "string" || blob.constructor===toString ){ try{ return saver( "data:" + mimeType + ";base64," + self.btoa(blob) ); }catch(y){ return saver( "data:" + mimeType + "," + encodeURIComponent(blob) ); } } // Blob but not URL support: reader=new FileReader(); reader.onload=function(e){ saver(this.result); }; reader.readAsDataURL(blob); } return true; }; /* end download() */ })); </script> <!-- end: download.js --> <!-- ************************************* embedded JavaScript library (used to show text differences between two user.js) name: jsdiff.js version: author: John Resig URL: https://johnresig.com/projects/javascript-diff-algorithm/#postcomment URL: https://johnresig.com/files/jsdiff.js license: MIT license ***** MODIFIED: disabled the escape function ***** ************************************* --> <script type="text/javascript"> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) * Modified by Chu Alan "sprite" * * Released under the MIT license. * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function escape(s) { var n = s; // n = n.replace(/&/g, "&"); // n = n.replace(/</g, "<"); // n = n.replace(/>/g, ">"); // n = n.replace(/"/g, """); return n; } function diffString( o, n ) { o = o.replace(/\s+$/, ''); n = n.replace(/\s+$/, ''); var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) ); var str = ""; var oSpace = o.match(/\s+/g); if (oSpace == null) { oSpace = ["\n"]; } else { oSpace.push("\n"); } var nSpace = n.match(/\s+/g); if (nSpace == null) { nSpace = ["\n"]; } else { nSpace.push("\n"); } if (out.n.length == 0) { for (var i = 0; i < out.o.length; i++) { str += '<del>' + escape(out.o[i]) + oSpace[i] + "</del>"; } } else { if (out.n[0].text == null) { for (n = 0; n < out.o.length && out.o[n].text == null; n++) { str += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>"; } } for ( var i = 0; i < out.n.length; i++ ) { if (out.n[i].text == null) { str += '<ins>' + escape(out.n[i]) + nSpace[i] + "</ins>"; } else { var pre = ""; for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { pre += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>"; } str += " " + out.n[i].text + nSpace[i] + pre; } } } return str; } function randomColor() { return "rgb(" + (Math.random() * 100) + "%, " + (Math.random() * 100) + "%, " + (Math.random() * 100) + "%)"; } function diffString2( o, n ) { o = o.replace(/\s+$/, ''); n = n.replace(/\s+$/, ''); var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) ); var oSpace = o.match(/\s+/g); if (oSpace == null) { oSpace = ["\n"]; } else { oSpace.push("\n"); } var nSpace = n.match(/\s+/g); if (nSpace == null) { nSpace = ["\n"]; } else { nSpace.push("\n"); } var os = ""; var colors = new Array(); for (var i = 0; i < out.o.length; i++) { colors[i] = randomColor(); if (out.o[i].text != null) { os += '<span style="background-color: ' +colors[i]+ '">' + escape(out.o[i].text) + oSpace[i] + "</span>"; } else { os += "<del>" + escape(out.o[i]) + oSpace[i] + "</del>"; } } var ns = ""; for (var i = 0; i < out.n.length; i++) { if (out.n[i].text != null) { ns += '<span style="background-color: ' +colors[out.n[i].row]+ '">' + escape(out.n[i].text) + nSpace[i] + "</span>"; } else { ns += "<ins>" + escape(out.n[i]) + nSpace[i] + "</ins>"; } } return { o : os , n : ns }; } function diff( o, n ) { var ns = new Object(); var os = new Object(); for ( var i = 0; i < n.length; i++ ) { if ( ns[ n[i] ] == null ) ns[ n[i] ] = { rows: new Array(), o: null }; ns[ n[i] ].rows.push( i ); } for ( var i = 0; i < o.length; i++ ) { if ( os[ o[i] ] == null ) os[ o[i] ] = { rows: new Array(), n: null }; os[ o[i] ].rows.push( i ); } for ( var i in ns ) { if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) { n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] }; o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] }; } } for ( var i = 0; i < n.length - 1; i++ ) { if ( n[i].text != null && n[i+1].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1 }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 }; } } for ( var i = n.length - 1; i > 0; i-- ) { if ( n[i].text != null && n[i-1].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1 }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 }; } } return { o: o, n: n }; } </script> <!-- end: jsdiff.js --> <!-- ************************************* JavaScript functions - other sources code from or based on sources as shown ************************************* --> <script type="text/javascript"> // ************************************* // RegExp.escape // ************************************* // function to escape a variable for use in regex // https://stackoverflow.com/questions/6318710/javascript-equivalent-of-perls-q-e-or-quotemeta RegExp.escape = function(text) { return (text+'').replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } // ************************************* // b64EncodeUnicode/b64DecodeUnicode // ************************************* // functions to base64 encode/decode (and cope with Unicode characters) // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem function b64EncodeUnicode(str) { // first we use encodeURIComponent to get percent-encoded UTF-8, // then we convert the percent encodings into raw bytes which // can be fed into btoa. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) { return String.fromCharCode('0x' + p1); })); } function b64DecodeUnicode(str) { // Going backwards: from bytestream, to percent-encoding, to original string. return decodeURIComponent(atob(str).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } // ************************************* // computedStyle // ************************************* // function to get element style values // https://stackoverflow.com/questions/6134471/using-elements-that-are-added-to-an-array-with-document-getelementbyidid/6134501#6134501 // var element = document.getElementById('Img3'); // alert(computedStyle(element,'width')); var computedStyle = function (el,style) { var cs; if (typeof el.currentStyle != 'undefined') { cs = el.currentStyle; } else { cs = document.defaultView.getComputedStyle(el,null); } return cs[style]; } // ************************************* // escapeHtml // ************************************* // function to replace & < > " ' characters with HTML escape codes // action = decode+quotes|encode+quotes|decode|encode // (blank or anything else = encode) // based on code from: // https://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406 function escapeHtml(text, action) { if (action == "decode+quotes") { // decode (+ quotes) var map = { '&': '&', '<': '<', '>': '>', '"': '"', ''': "'" }; return text.replace(/&|<|>|"|'/g, function(m) { return map[m]; }); } else if (action == "encode+quotes") { // encode (+ quotes) var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, function(m) { return map[m]; }); } else if (action == "decode") { // decode var map = { '&': '&', '<': '<', '>': '>' }; return text.replace(/&|<|>/g, function(m) { return map[m]; }); } else { // encode var map = { '&': '&', '<': '<', '>': '>' }; return text.replace(/[&<>]/g, function(m) { return map[m]; }); } } // ************************************* // changeClass // ************************************* // based on code from: // https://stackoverflow.com/questions/195951/change-an-elements-css-class-with-javascript function changeClass(object,oldClass,newClass) { if (oldClass == "") { if ( ! /(?:^|\s)newClass(?!\S)/.test(object.className) ) { object.className += " "+newClass; } } else { var regExp = new RegExp('(?:^|\\s)' + oldClass + '(?!\\S)', 'ig'); object.className = object.className.replace( regExp , newClass ); } } // ************************************* // getURLVariable // ************************************* /* process URL variables */ // https://stackoverflow.com/questions/831030/how-to-get-get-request-parameters-in-javascript function getURLVariable(name){ // window.location.search.substring(1) if (name=(new RegExp('[?&]' + encodeURIComponent(name) + '=([^&#]*)')).exec(location.search)) return decodeURIComponent(name[1]) } // ************************************* // downloadFile // ************************************* // based on code from: // https://stackoverflow.com/questions/196498/how-do-i-load-the-contents-of-a-text-file-into-a-javascript-variable // https://stackoverflow.com/questions/6348207/making-a-paragraph-in-html-contain-a-text-from-a-file // https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data // https://cameronnokes.com/blog/4-common-mistakes-front-end-developers-make-when-using-fetch/ // browser security policies prevent same origin local file:// loading function downloadFile(url, text_box){ var e = document.getElementById(text_box); var msg = "\nTroubleshooting: Check connection?" + " Blocked by an extension (eg uMatrix XHR)?" + " Site allows fetch? Valid URL/file?" + " Visit the URL and copy/paste the content instead?\n" + url; clearTextAreaContents(text_box); toggleTextAreaReadOnly(text_box, true, false); fetch(url) .then(function(response) { if (response.ok) { return response.text() } else { throw Error(response.status); } }) .then(function(text) { e.value = text; }) .catch(function(err) { alert("File fetch error:\n" + err.message + msg); }); } // end function downloadFile // ************************************* // loadButtonAction // ************************************* function loadButtonAction(select, input, text_box, filenameforsave){ var url = select.value; select.selectedIndex = 0; if (url == "LOCAL") { // trigger the (hidden) local file load selector (does not work for Fennec) if ( document.getElementById("collect_button").style.textDecoration == "line-through" && (text_box == "box_1_template" || text_box == "box_2_overrides") ) { return; } document.getElementById(input).click(); } else if (url == "BUTTON") { // show the Browse button (Fennec workaround) for (const id of [ "template", "overrides", "userjs", "other" ]) { if (document.getElementById("load_" + id + "_input").style.display === "block") { document.getElementById("load_" + id + "_input").style.display = "none"; document.getElementById("loadsave_" + id + "_select").options[2].textContent = document.getElementById("loadsave_" + id + "_select").options[2].textContent .replace(/\u{25A3}/u, "\u25A2"); } else { document.getElementById("load_" + id + "_input").style.display = "block"; document.getElementById("loadsave_" + id + "_select").options[2].textContent = document.getElementById("loadsave_" + id + "_select").options[2].textContent .replace(/\u{25A2}/u, "\u25A3"); } } } else if (url == "SAVE") { download(document.getElementById(text_box).value, filenameforsave, "application/javascript"); } else if (/^.+$/.test(url)) { if ( document.getElementById("collect_button").style.textDecoration == "line-through" && (text_box == "box_1_template" || text_box == "box_2_overrides") ) { return; } if (url == "URL") { url = ""; url = prompt("Please input URL of file to load\n" + "(Only works if the site allows this)\n" + "(eg arkenfox /master/ or /vNN.0-beta/ /NN.0/)", "https://raw.githubusercontent.com/arkenfox/user.js/master/user.js"); } if (url != null && url != "") { downloadFile(url, text_box); setTimeout(function(){ toggleTextAreaReadOnly(text_box, true); e.selectionStart = 0; e.selectionEnd = 0; e.focus(); }, 1000); } } } // end function loadButtonAction // ************************************* // loadLocalFile / drag and drop // ************************************* // based on code from: // https://www.html5rocks.com/en/tutorials/file/dndfiles/ // https://stackoverflow.com/questions/16215771/how-open-select-file-dialog-via-js/16215950 // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop function loadLocalFile(ev, text_box){ // get files from Browse selection or drag and drop var files = []; if (typeof ev == 'string') { var files = document.getElementById(ev).files; } else { // dropHandler(ev) // Prevent default behavior (Prevent file from being opened) ev.preventDefault(); if (ev.dataTransfer.items) { // Use DataTransferItemList interface to access the file(s) for (var i = 0; i < ev.dataTransfer.items.length; i++) { // If dropped items aren't files, reject them if (ev.dataTransfer.items[i].kind === 'file') { files.push(ev.dataTransfer.items[i].getAsFile()); } } } else { // Use DataTransfer interface to access the file(s) for (var i = 0; i < ev.dataTransfer.files.length; i++) { files.push(ev.dataTransfer.files[i]); } } } // determine which box to load the selected files var fe, ft, fo, fu, fx; // filename for each box (fe is current box) if (!files.length) { // no files selected return; } else if (files.length > 4) { alert("Too many files selected. (" + files.length + " > maximum 4)"); return; } else if (files.length == 1) { // one file selected (load into current box) fe=files[0]; } else { // multiple files selected (load into corresponding boxes) // loop through the file list var matched = "", noMatch = ""; for (var i=0, f; f=files[i]; i++) { if ( (/^user\.js.*$/.test(f.name)) && (!fu) ) { if (!fu) { fu=f; } else { noMatch += "\n?: " + f.name; } } else if ( (/^user-overrides.*\.js$/.test(f.name)) && (!fo) ) { if (!fo) { fo=f; } else { noMatch += "\n?: " + f.name; } } else if ( (/user-template.*\.js$/.test(f.name)) && (!ft) ) { if (!ft) { ft=f; } else { noMatch += "\n?: " + f.name; } } else if ( (/^.*\.js$/.test(f.name)) && (!fx) ) { if (!fx) { fx=f; } else { noMatch += "\n?: " + f.name; } } else { noMatch += "\n?: " + f.name; } } matched = "\n1: " if (ft) { matched += ft.name ; } matched += "\n2: " if (fo) { matched += fo.name ; } matched += "\n3: " if (fu) { matched += fu.name ; } matched += "\n4: " if (fx) { matched += fx.name ; } if (noMatch) { noMatch = "\n\nNot matched to a box:" + noMatch; } if (!confirm("Load into boxes?" + matched + noMatch)) { return; } } // separate reads and variables (to avoid delayed loads into wrong box) if (fe) { var e = document.getElementById(text_box); var reader_e = new FileReader(); // If we use onloadend, we need to check the readyState. reader_e.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { // DONE == 2 // if (typeof evt.target.result == 'string') { e.value = evt.target.result; // } // else { // let utf8decoder = new TextDecoder('utf-8'); // e.value = utf8decoder.decode(decomp(evt.target.result)); // } // e.select(); toggleTextAreaReadOnly(text_box, true); e.selectionStart = 0; e.selectionEnd = 0; e.focus(); } }; reader_e.readAsText(fe); // reader_e.readAsArrayBuffer(fe); // reader_e.readAsBinaryString(fe); } if (ft) { var t = document.getElementById("box_1_template"); var reader_t = new FileReader(); // If we use onloadend, we need to check the readyState. reader_t.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { // DONE == 2 t.value = evt.target.result; toggleTextAreaReadOnly("box_1_template", true); // t.select(); t.selectionStart = 0; t.selectionEnd = 0; t.focus(); } }; reader_t.readAsText(ft); } if (fo) { var o = document.getElementById("box_2_overrides"); var reader_o = new FileReader(); // If we use onloadend, we need to check the readyState. reader_o.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { // DONE == 2 o.value = evt.target.result; toggleTextAreaReadOnly("box_2_overrides", true); // o.select(); o.selectionStart = 0; o.selectionEnd = 0; o.focus(); } }; reader_o.readAsText(fo); } if (fu) { var u = document.getElementById("box_3_userjs"); var reader_u = new FileReader(); // If we use onloadend, we need to check the readyState. reader_u.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { // DONE == 2 u.value = evt.target.result; toggleTextAreaReadOnly("box_3_userjs", true); // u.select(); u.selectionStart = 0; u.selectionEnd = 0; u.focus(); } }; reader_u.readAsText(fu); } if (fx) { var x = document.getElementById("box_4_other"); var reader_x = new FileReader(); // If we use onloadend, we need to check the readyState. reader_x.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { // DONE == 2 x.value = evt.target.result; toggleTextAreaReadOnly("box_4_other", true); // t.select(); x.selectionStart = 0; x.selectionEnd = 0; x.focus(); } }; reader_x.readAsText(fx); } } // end function loadLocalFile function dragOverHandler(ev) { // Prevent default behavior (Prevent file from being opened) ev.preventDefault(); } function dropHandler1(ev) { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { loadLocalFile(ev, "box_1_template"); } } function dropHandler2(ev) { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { loadLocalFile(ev, "box_2_overrides"); } } function dropHandler3(ev) { loadLocalFile(ev, "box_3_userjs"); } function dropHandler4(ev) { loadLocalFile(ev, "box_4_other"); } // ************************************* // amendCodeComments // ************************************* /* function to either: (1) remove javascript comments, or (2) convert in-block comments to in-line based on removeCodeComments function from: https://stackoverflow.com/questions/5989315/regex-for-match-replacing-javascript-comments-both-multiline-and-inline#52630274 and modified to add: convertInBlockToInLine (optional) asteriskPosition (fix) */ function amendCodeComments(code, convertInBlockToInLine) { var inQuoteChar = null; var inBlockComment = false; var asteriskPosition = null; var inLineComment = false; var inRegexLiteral = false; var newCode = ''; for (var i=0,j=code.length;i<j;i++) { if (!inQuoteChar && !inBlockComment && !inLineComment && !inRegexLiteral) { if (code[i] === '"' || code[i] === "'" || code[i] === '`') { inQuoteChar = code[i]; } else if (code[i] === '/' && code[i+1] === '*') { inBlockComment = true; asteriskPosition = i+1; if (convertInBlockToInLine) { newCode += '// '; } } else if (code[i] === '/' && code[i+1] === '/') { inLineComment = true; } else if (code[i] === '/' && code[i+1] !== '/') { inRegexLiteral = true; } } else { if ( inQuoteChar && ((code[i] === inQuoteChar && code[i-1] != '\\') || (code[i] === '\n' && inQuoteChar !== '`')) ) { inQuoteChar = null; } if ( inRegexLiteral && ((code[i] === '/' && code[i-1] !== '\\') || code[i] === '\n') ) { inRegexLiteral = false; } /* ensure asterisk is fresh to handle slash after opening comment */ if ( inBlockComment && code[i-2] === '*' && code[i-1] === '/' && i-2 > asteriskPosition ) { inBlockComment = false; asteriskPosition = null; if (convertInBlockToInLine && code[i] !== '\n') { newCode += '\n'; } } if (inLineComment && code[i] === '\n') { inLineComment = false; } } if ((!inBlockComment && !inLineComment) || convertInBlockToInLine) { newCode += code[i]; } if (convertInBlockToInLine && inBlockComment && code[i] === '\n') { newCode += '// '; } } return newCode; } /* end function amendCodeComments */ </script> <!-- end: JavaScript functions - other sources --> <!-- ************************************* JavaScript functions - userjs-tool.html ************************************* --> <script type="text/javascript"> // ************************************* // updateDateTimeStampVariable // ************************************* function returnDateTime() { var d = new Date(); // var months = ["Jan","Feb","Mar","Apr","May","Jun", // "Jul","Aug","Sep","Oct","Nov","Dec"]; // var days = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; // Date.now() Get the time. ECMAScript 5. return d.getFullYear() // + months[d.getMonth()] + ((d.getMonth()+1) < 10 ? "0" : "") + (d.getMonth()+1) + (d.getDate() < 10 ? "0" : "") + d.getDate() + "_" // + days[d.getDay()] + (d.getHours() < 10 ? "0" : "") + d.getHours() + (d.getMinutes() < 10 ? "0" : "") + d.getMinutes() + (d.getSeconds() < 10 ? "0" : "") + d.getSeconds(); // + "." + getMilliseconds(); } function updateDateTimeStampVariable() { date_time_stamp = returnDateTime(); } // ************************************* // invertColorForClass // ************************************* function invertColorForClass(classname) { var newbg = computedStyle(document.getElementById("view_area"),'color'); var newfg = computedStyle(document.getElementById("view_area"),'backgroundColor'); if ( (newbg == 'rgb(0, 0, 0)' && newfg == 'rgba(0, 0, 0, 0)') || (newbg == 'rgba(0, 0, 0, 0)' && newfg == 'rgb(0, 0, 0)') || (newbg == newfg) ) { newfg = "#FFFFFF"; /*White*/ newbg = "#000000"; /*Black*/ } var e = document.getElementsByClassName(classname); for (var i=0,j=e.length;i<j;i++) { e[i].style.color = newfg; e[i].style.backgroundColor = newbg; } } // ************************************* // clickActionForCollectMode // ************************************* /* collect mode - add/amend pref to collected output */ function clickActionForCollectMode() { var output_box = document.getElementById("box_2_overrides"); var prefstatus, prefName, prefValue, quote_type = ""; var new_value = null, new_inactive = null; var x, x2, r; // prefstatus was clicked eg active=user_pref, inactive=// user_pref // toggle active/inactive (without prefValue change) if (/pref/.test(this.className)) { prefstatus = this; prefName = prefstatus.nextElementSibling; prefValue = prefName.nextElementSibling; if (/^[ \t]*(\/\/|\/\*)[ \t]*user_pref/.test(prefstatus.innerHTML)) { // inactive to active new_inactive = ""; prefstatus.innerHTML = "user_pref" } else { // active to inactive new_inactive = "// "; prefstatus.innerHTML = "// user_pref" } // underline "user_pref" or "// user_pref" to indicate change prefstatus.style.textDecoration = "underline"; } // prefValue was clicked else { prefValue = this; prefName = prefValue.previousElementSibling; prefstatus = prefName.previousElementSibling; // only if user_pref is active (as in not // commented) if (!(/^[ \t]*(\/\/|\/\*)[ \t]*user_pref/.test(prefstatus.innerHTML))) { // toggle boolean, others prompt edit (remove quotes on strings first) if (/true/.test(prefValue.className)) { new_value = "false"; prefValue.className = prefValue.className.replace(/true/, "false"); } else if (/false/.test(prefValue.className)) { new_value = "true"; prefValue.className = prefValue.className.replace(/false/, "true"); } else { // string and integer if (/string/.test(prefValue.className)) { quote_type = prefValue.innerHTML[0]; if (!(quote_type == "'" || quote_type == '"')) { quote_type = "" } else { } // remove quotes new_value = prefValue.innerHTML.replace(/^["'](.*)["']$/,"$1"); } else { new_value = prefValue.innerHTML; } // show edit box (you get null if canceled) new_value = prompt("Please amend the value", new_value); } // validate (add quotes on strings) if (new_value != null) { if (/string/.test(prefValue.className)) { prefValue.innerHTML = quote_type + new_value + quote_type; } else if (/integer/.test(prefValue.className)) { if (Number.isInteger(Number(new_value))) { prefValue.innerHTML = new_value; } else { new_value = null; } } else { prefValue.innerHTML = new_value; } if (new_value != null) { // underline value to indicate change prefValue.style.textDecoration = "underline"; } } } /* end if (that stops edit of inactive prefs) */ } // add to list of changes (kept in box_2_overrides) if (new_value != null || new_inactive != null) { x = new RegExp("user_pref[ \t]*\\([ \t]*[\"']" + RegExp.escape(prefName.innerHTML) + "[\"']", "m"); x2 = new RegExp("^[ \t]*\/\/\/\/ --- comment-out --- '" + RegExp.escape(prefName.innerHTML) + "'", "m"); if (new_inactive == "" || new_inactive == null) { // ie: r will be: user_pref("name", value); r = "user_pref(\"" + prefName.innerHTML + "\", " + prefValue.innerHTML + ");"; } else { r = "//// --- comment-out --- '" + prefName.innerHTML + "'"; } if ( x.test(output_box.value) || x2.test(output_box.value) ) { // existing in output - do a find and replace // keep any succeeding comment x = new RegExp("^.*user_pref[ \t]*\\([ \t]*[\"']" + RegExp.escape(prefName.innerHTML) + "[\"']" + "[ \t]*,[ \t]*(.*)[ \t]*\\)[ \t]*;" + "(.*)$", "gm"); // use "$2 // $1" if you want previous prefValue output_box.value = output_box.value.replace(x,r + "$2"); x2 = new RegExp("^[ \t]*\/\/\/\/ --- comment-out --- '" + RegExp.escape(prefName.innerHTML) + "'(.*)$", "gm"); output_box.value = output_box.value.replace(x2,r + "$1"); } else { // new to output output_box.value += r + "\n"; } // get index of prefName within text box var ns = output_box.value.lastIndexOf('"' + prefName.innerHTML + '"'); if (ns == -1) { ns = output_box.value.lastIndexOf("'" + prefName.innerHTML + "'"); } var ne = ns; if (ns != -1) { ns = ns + 1; ne = ns + prefName.innerHTML.length; } // set focus to that location if (ns != -1) { output_box.scrollIntoView(); output_box.select; output_box.selectionEnd = ne; output_box.selectionStart = ns; // it loses focus despite next line output_box.focus(); } } } /* end function clickActionForCollectMode */ // ************************************* // addEventListenersForCollectMode // ************************************* function addEventListenersForCollectMode() { // https://stackoverflow.com/questions/37255293/getelementsbyclassname-with-two-classes var class1 = Array.from(document.getElementsByClassName("true")); var class2 = Array.from(document.getElementsByClassName("false")); var class3 = Array.from(document.getElementsByClassName("integer")); var class4 = Array.from(document.getElementsByClassName("string")); var class5 = Array.from(document.getElementsByClassName("pref")); var e = Array.from(new Set(class1.concat(class2,class3,class4,class5))); for (var i=0,j=e.length;i<j;i++) { e[i].style.cursor = "cell"; e[i].addEventListener("click", clickActionForCollectMode); } } // ************************************* // removeEventListenersForCollectMode // ************************************* function removeEventListenersForCollectMode() { // https://stackoverflow.com/questions/37255293/getelementsbyclassname-with-two-classes var class1 = Array.from(document.getElementsByClassName("true")); var class2 = Array.from(document.getElementsByClassName("false")); var class3 = Array.from(document.getElementsByClassName("integer")); var class4 = Array.from(document.getElementsByClassName("string")); var class5 = Array.from(document.getElementsByClassName("pref")); var e = Array.from(new Set(class1.concat(class2,class3,class4,class5))); for (var i=0,j=e.length;i<j;i++) { e[i].style.cursor = "auto"; e[i].removeEventListener("click", clickActionForCollectMode); } } // ************************************* // tidySectionHeading // ************************************* // used in userjsViewer and userjsReduce function tidySectionHeading(text, noInserts) { function tidySectionHeadingRegex(f,r) { var x = new RegExp(f, "i"); text = text.replace(x, r); } // arkenfox style tidySectionHeadingRegex("^(?:\/\/ )?[ *\t]*[/][*][*][*-][ *\t]*\\[SECTION ([^\\]]*)\\]:[ \t]*(.*)[ *\t]*$", "$1: $2"); if (!noInserts) { tidySectionHeadingRegex("^(?:\/\/ )?\/\\* *(START).*$", "0000: $1"); tidySectionHeadingRegex("^(?:\/\/ )?\/\\* *(END).*$", "9999~ $1"); } // pyllyukko style tidySectionHeadingRegex("^(?:\/\/ )?[ *\t]*SECTION:[ \t]*(.*)[ *\t]*$", "$1"); // 12bytes style tidySectionHeadingRegex("^(?:\/\/ )?[ *=]*(WARINING.*DO NOT EDIT THIS FILE EXCEPT AS INSTRUCTED BELOW.*)$", "12bytes.org"); tidySectionHeadingRegex("^(?:\/\/ )?[ *=]*(12BYTES\.ORG.*)$", "$1"); tidySectionHeadingRegex("^(?:\/\/ )? [*] === (.*)$", "$1"); tidySectionHeadingRegex("^(?:\/\/ )?[ *=]*(END 12BYTES\.ORG CUSTOMIZATION.*)$", "$1"); tidySectionHeadingRegex("^(?:\/\/ )?[ *=]*(BEGIN USER CUSTOMIZATION.*)$", "$1"); tidySectionHeadingRegex("^(?:\/\/ )?[ *=]*(END USER CUSTOMIZATION.*)$", "$1"); // crssi style tidySectionHeadingRegex("^(?:\/\/ )?[ *=]*(HOME.*crssi.*)$", "crssi"); // remove leading/trailing /*** etc tidySectionHeadingRegex("^[/][*][*][*-] *([^*])", "$1"); // some leading/trailing character removal - eg ***/ === /*** etc tidySectionHeadingRegex("[ \t*=/-]*$", ""); tidySectionHeadingRegex("^[ \t*=/-]*", ""); // replace & < > characters with HTML escape codes return(escapeHtml(text)); } /* end function tidySectionHeading */ // ************************************* // detectSectionHeading // ************************************* // used in userjsViewer and userjsReduce function detectSectionHeading(line, userjs_type, line_number) { // narga style if (userjs_type == "narga" && line_number > 7) { if (new RegExp("^(?:\/\/ )?[ \t]+[*][^/]").test(line)) { return(true); } else { return(false); } } // arkenfox style else if (new RegExp("^(?:\/\/ )?[/][*][*][*-] *([^*])").test(line)) { return(true); } else if (new RegExp("^(?:\/\/ )?[/][*] *(START|END).*$").test(line)) { return(true); } // pyllyukko style else if (new RegExp("^(?:\/\/ )?[ *\t]*SECTION:[ \t]*(.*)[ *\t]*$").test(line)) { return(true); } // 12bytes style else if (new RegExp("^(?:\/\/ )?[ *=]*(WARINING.*DO NOT EDIT THIS FILE EXCEPT AS INSTRUCTED BELOW.*)$").test(line)) { return(true); } else if (new RegExp("^(?:\/\/ )?[ *=]*(12BYTES\.ORG.*)$").test(line)) { return(true); } else if (new RegExp("^(?:\/\/ )? [*] === (.*)$").test(line)) { return(true); } else if ( new RegExp("^(?:\/\/ )?[ *=]*(END 12BYTES\.ORG CUSTOMIZATION.*)$").test(line) ) { return(true); } else if ( new RegExp("^(?:\/\/ )?[ *=]*(BEGIN USER CUSTOMIZATION.*)$").test(line) ) { return(true); } else if ( new RegExp("^(?:\/\/ )?[ *=]*(END USER CUSTOMIZATION.*)$").test(line) ) { return(true); } // crssi style else if (new RegExp("^(?:\/\/ )?[ *=]*(HOME.*crssi.*)$").test(line)) { return(true); } // krathalan style else if ( new RegExp("^(?:\/\/ )?[ \t]*\/[/*][ \t]*--*[ \t]*([0-9]+[. ]|Section)").test(line) ) { return(true); } else { return(false); } } /* end function detectSectionHeading */ // ************************************* // detectUserjsType // ************************************* // used in userjsViewer and userjsReduce function detectUserjsType(line, userjs_type, line_number) { // determine type (within first 10 lines) if (userjs_type == "" && line_number < 10) { if ( new RegExp("^(?:\/\/ )?\\**[ \t]*name:[ \t]*(arkenfox|ghacks).user.js", "i").test(line) ) { userjs_type = "arkenfox"; } else if (new RegExp("pyllyukko", "i").test(line)) { userjs_type = "pyllyukko"; } else if (new RegExp("NARGA", "i").test(line)) { userjs_type = "narga"; } } return userjs_type; } /* end function detectUserjsType */ // ************************************* // userjsViewer // ************************************* /* build and display userjs as HTML */ function userjsViewer(text_box_name, convert_code_comments) { // update date of hidden heading (H1/H3/DL tags for bookmarks import compatibility) updateDateTimeStampVariable(); document.getElementById("hiddendate").innerHTML = document.getElementById("hiddendate").innerHTML .replace(/(--userjs_)[0-9_]+/, "$1" + date_time_stamp); // get content and clear input and page document.getElementById("view_area").innerHTML = ""; var content; // if View+ style is selected in the menu if (typeof convert_code_comments !== "boolean") { convert_code_comments = toggleViewPlusOnView("status"); } // convert in-block comments to in-line // (improves inactive pref recognition) if (convert_code_comments) { content = amendCodeComments(document.getElementById(text_box_name).value, true) .replace(/(\r\n|\r)/g,'\n').split("\n"); } else { content = document.getElementById(text_box_name).value .replace(/(\r\n|\r)/g,'\n').split("\n"); } var theme = document.body.className.replace( /(^| *)[^_]+_/ , ''); // variables for holding HTML that we build from the content var index_select_html = '' + ' <option value="" disabled selected hidden>▾Index</option>\n'; var groups_container_html = ''; var content_html = ''; var section_heading = '(TOP) / Introduction'; // if there is only 1 section we will not collapse the viewer var section_count = 1; var userjs_type = ""; var group_user_pref_list = ''; var previousLineWasBlank = true, currentLineWasBlank; // some common HTML used for each section_heading function appendSectionStartHtml() { index_select_html += ' <option>' + section_heading + '</option>\n'; groups_container_html += '<a target="_blank" href="about:config?filter=/^\\*$|^('; content_html += "" // plus below // hidden heading (H1/H3/DL tags for bookmarks import compatibility) + ' <div class="hidden"><H3>' + section_heading + '</H3></div>\n' + ' <button type="button" class="controls borders ' + 'controls_' + theme + ' borders_' + theme + ' heading_buttons" id="' + section_heading + '">' + section_heading + '</button>\n' + ' <div class="content">\n'; } function appendSectionEndHtml() { // remove trailing '|' group_user_pref_list = group_user_pref_list.replace(/\|$/, ''); groups_container_html += group_user_pref_list + ')(;|$)|^$/i">-' + section_heading + '</A><br>\n'; content_html += '</div><br>\n\n\n'; // end section group_user_pref_list = ''; } /* work through each line of the user.js content */ // detect headings, user_pref, values, and URLs // insert color class styles and section blocks // by appending code to: index_select_html groups_container_html content_html appendSectionStartHtml(); var i = 0; for (i in content) { var line = content[i]; if (/^[ \t]*$/.test(line)) { currentLineWasBlank = true; } else { currentLineWasBlank = false; } userjs_type = detectUserjsType(line,userjs_type,i); // on section_heading detection: end previous section, start new section if ( detectSectionHeading(line,userjs_type,i) == true ) { if (!previousLineWasBlank) { content_html += "\n" } previousLineWasBlank = true; appendSectionEndHtml(); section_heading = tidySectionHeading(line); appendSectionStartHtml(); section_count += 1; } // HTML code injection prevention // replace & < > characters with HTML escape codes line = escapeHtml(line); // on user_pref lines add span and class tags // (for syntax highlighting pretty colors) var x,r; var line_userpref_part = ""; x = new RegExp("^(.*user_pref)" // $1 user_pref //user_pref etc + "([ \t]*\\([ \t]*[\"'])" // $2 (" (' + "([^\"']+)" // $3 prefname + "([\"'][ \t]*,[ \t]*)" // $4 ", ', + "(.*)" // $5 prefvalue "prefvalue" 'prefvalue' + "([ \t]*\\)[ \t]*;)" // $6 ); + "(.*)$", "gi"); // $7 afters/comment if (x.test(line)) { // separate line into: line_userpref_part) user_pref statement line) afters/comment line_userpref_part = line.replace(x,"$1$2$3$4$5$6"); line = line.replace(x,"$7"); // add some HTML (tag and class for colors) // a/href for: prefname (turn into an about:config link) x = new RegExp("^(.*user_pref[ \t]*\\([ \t]*[\"'])" + "([^\"']+)([\"'].*)$", "i"); r = "$2"; var pref_escaped = line_userpref_part.replace(x, r).replace(/([*.+])/g, "\\$1"); r = "$1<a target=\"_blank\" href=\"about:config?filter=/^\\*\$|^(" + pref_escaped + ")(;|\$)|^\$/i\">$2</A>$3"; line_userpref_part = line_userpref_part.replace(x, r); // add prefname to groups group_user_pref_list += pref_escaped + "|"; // span/class for: //user_pref /*user_pref user_pref if (new RegExp("([/][/*]+[ \t]*user_pref)", "i").test(line_userpref_part)) { // for: "// user_pref" "/* user_pref" x = new RegExp("([/][/*]+[ \t]*user_pref)", "i"); r = '<span class="pref">$1</span>'; line_userpref_part = line_userpref_part.replace(x, r); } else { // for: "user_pref" (without // or /* immediately before) //x = new RegExp("([ /*\t]*user_pref)", "i"); x = new RegExp("(user_pref)", "i"); r = '<span class="pref">$1</span>'; line_userpref_part = line_userpref_part.replace(x, r); } // span/class for: false (prefvalue) if (new RegExp(",[ \t]*false[ \t]*\\);", "i").test(line_userpref_part)) { x = new RegExp("(,[ \t]*)(false)([ \t]*\\);)", "i"); r = '$1<span class="false">$2</span>$3'; line_userpref_part = line_userpref_part.replace(x, r); } // span/class for: true (prefvalue) else if (new RegExp(",[ \t]*true[ \t]*\\);", "i").test(line_userpref_part)) { x = new RegExp("(,[ \t]*)(true)([ \t]*\\);)", "i"); r = '$1<span class="true">$2</span>$3'; line_userpref_part = line_userpref_part.replace(x, r); } // span/class for: integer (prefvalue) else if (new RegExp(",[ \t]*[0-9.+-]+[ \t]*\\);", "i").test(line_userpref_part)) { x = new RegExp("(,[ \t]*)([0-9.+-]+)([ \t]*\\);)", "i"); r = '$1<span class="integer">$2</span>$3'; line_userpref_part = line_userpref_part.replace(x, r); } // span/class for: "string" 'string' (prefvalue) else { x = new RegExp("(,[ \t]*)([\"']*.*[\"']*)([ \t]*\\);)", "i"); r = '$1<span class="string">$2</span>$3'; line_userpref_part = line_userpref_part.replace(x, r); } // span/class for: ////OVERRIDE: ////COMMENT-OUT: x = new RegExp("\/\/\/\/(OVERRIDE|COMMENT-OUT):", "gi"); r = '<span class="overrides">$&</span>'; line_userpref_part = line_userpref_part.replace(x, r); } // a/href for: HTTP/HTTPS URLs (turn into a link) x = new RegExp("(https?:[/][/][^ \"']+[^\\)\\], \"'.;])", "gi"); r = '<a target="_blank" rel="external noopener noreferrer" href="$1" class="http">' + '<span class="hidden">__</span>$1</A>'; line = line.replace(x, r); // span/class for: [WARNING] x = new RegExp("(\\[WARNING\\]|\\[WARNING.)", "gi"); r = '<span class="warn">$1</span>'; line = line.replace(x, r); // span/class for: (hidden pref) [HIDDEN PREF] x = new RegExp("([\\[\\(]hidden pref[^\\)\\]]*[\\)\\]])", "gi"); r = '<span class="hid">$1</span>'; line = line.replace(x, r); // span/class for: [SETUP] [SETUP-...] x = new RegExp("(\\[SETUP\\]|\\[SETUP-[^\\]]+\\]|\/\/[ \t]NOTICE-DISABLED:)", "gi"); r = '<span class="setup">$1</span>'; line = line.replace(x, r); // span/class for: arkenfox ref if (line_userpref_part == "" && userjs_type == "arkenfox") { // for: /* 1234: /*** 1234: // ref in bold and color whole line x = new RegExp("^((?:\/\/ )?[/*]+ )([0-9][0-9][0-9][0-9][^:]*)(:)(.*?)([ /*]*)?$", "gi"); r = ""; if (convert_code_comments) { // add extra newline (if the replace matches) if (!previousLineWasBlank) { r = "\n" } if (x.test(line)) { previousLineWasBlank = true; } } r += '$1<span class="ref">$2</span><span class="ref" style="font-weight: normal">$3$4</span>$5'; line = line.replace(x, r); // for: /*** [SECTION 1234]: // ref in bold and color whole line x = new RegExp("^((?:\/\/ )?[/*]+ )(\\[SECTION [0-9][0-9][0-9][0-9][^\\]]*\\])(:)(.*?)([ /*]*)?$", "gi"); r = '$1<span class="ref">$2</span><span class="ref" style="font-weight: normal">$3$4</span>$5'; line = line.replace(x, r); // for: /* /** /*** if ( (convert_code_comments) && (!previousLineWasBlank) ) { x = new RegExp("^((?:\/\/ )?\\/\\*.*)$", "gi"); r = "\n$1"; line = line.replace(x, r); x = new RegExp("^((?:\/\/ )?\\/\\/ FF[0-9.+]+)$", "gi"); r = "\n$1"; line = line.replace(x, r); } } else if (line_userpref_part == "" && userjs_type == "pyllyukko") { // for: * SECTION: and // PREF: x = new RegExp("^((?:\/\/ )?[ *\t]*)((?:SECTION|PREF):.*)([ *\t]*)$", "gi"); // color r = '$1<span class="ref" style="font-weight: normal">$2</span>$3'; line = line.replace(x, r); } // add to HTML code content_html += line_userpref_part + line + '\n'; previousLineWasBlank = currentLineWasBlank; } /* end for (i in content) */ content = []; appendSectionEndHtml(); /* update HTML page with the new content */ content_html += ' <span class="anchor" id="(BOTTOM)"></span><br><br><br></DL>\n'; index_select_html += '<option>(BOTTOM)</option>\n'; document.getElementById("index_select").innerHTML = index_select_html; // also remove first group if unused document.getElementById("groups_container").innerHTML = groups_container_html.replace(new RegExp('<a target="_blank"' + ' href="about:config\\?filter=' + '\\/\\^\\\\\\*\\$\\|\\^\\(\\)\\(;\\|\\$\\)\\|\\^\\$\\/i">' + '-\\(TOP\\) \\/ Introduction<\\/A><br>'), ""); document.getElementById("view_area").innerHTML = content_html; index_select_html = null; groups_container_html = null; content_html = null; /* actions following the new content addition */ /* add listener for expand/collapse individual sections (multiple buttons) */ var e = document.getElementsByClassName("heading_buttons"); for (var i = 0, j = e.length; i<j; i++) { e[i].addEventListener("click", function() { var next = this.nextElementSibling; if (next.style.display === "block") { next.style.display = "none"; } else { next.style.display = "block"; } section_focus = this; }); } invertColorForClass("overrides"); togglePrefixAboutConfigLinks("refresh"); setWrap("refresh"); if (toggleExpandAllOnView("status") || section_count == 1) { setExpandAll(true); } if (toggleGroupsOnView("status")) { toggleGroupsPanel(true); } } /* end function userjsViewer */ // ************************************* // expandSectionWithFirstInstanceOfText // ************************************* function expandSectionWithFirstInstanceOfText(text) { if (!text) { text = prompt("Jump to the section containing the first instance" + " of text entered", getURLVariable("jump")); } if (text) { var regexText = new RegExp(RegExp.escape(text), "i"); var e = document.getElementsByClassName("content"); for (var i=0, j=e.length; i<j; i++) { // .innerHTML (.innerText || .textContent) if (regexText.test(e[i].textContent)) { // expand/show section e[i].style.display = "block"; section_focus = e[i]; refocusSection(); // highlight element (if text is not split across elements) var e2 = e[i].getElementsByTagName("*"); for (var i2 = 0, j2 = e2.length; i2 < j2; i2++) { if (regexText.test(e2[i2].textContent)) { e2[i2].className += " jump"; e2[i2].scrollIntoView(); window.scrollBy(0, -200); break; } } break; } } } } /* end function expandSectionWithFirstInstanceOfText */ // ************************************* // userjsAppend // ************************************* function userjsAppend(input_box_name, input_box_name_2, output_box_name) { var input_box = document.getElementById(input_box_name); var input_box_2 = document.getElementById(input_box_name_2); var output_box = document.getElementById(output_box_name); // convert in-block comments to in-line (improves inactive pref recognition) var append_content = amendCodeComments(input_box_2.value, true) .replace(/(\r\n|\r)/g,'\n').split("\n"); var x = new RegExp("^[ \t]*\/\/\/\/ --- add-override-comment ---[ \t]*$", "mi"); var override_tagging_on = x.test(input_box_2.value); output_box.value = ''; updateDateTimeStampVariable(); output_box.value += '// ' + date_time_stamp + " appended user-overrides.js to user-template.js\n\n" + input_box.value.replace(/[\r\n]*$/, "") + "\n\n\n\n\n" + "/*** (overrides) __________ " + "(start of overrides) __________ ***/\n" + 'user_pref("_user.js.parrot", "(overrides) (start of overrides)");' + "\n\n\n\n\n"; var i = 0; for (i in append_content) { var line = append_content[i]; var prefName; // (after amendCodeComments) change '// /*...*/' to '/*...*/' x = new RegExp("^\\/\\/ ([ \t]*\\/\\*.*\\*\\/[ \t]*)$", "gm"); line = line.replace(x, "$1"); // overrides: comment-out x = new RegExp("^[ \t]*\/\/\/\/ --- comment-out --- '([^']+)'.*$"); if (x.test(line)) { // override-out comment pref in the output prefName = line.replace(x, "$1"); x = new RegExp("user_pref[ \t]*\\([ \t]*[\"']" + RegExp.escape(prefName) + "[\"']", "gm"); output_box.value = output_box.value.replace(x, "\/\/\/\/COMMENT-OUT: $&"); // prevent duplicated ////COMMENT-OUT: x = new RegExp("\/\/\/\/COMMENT-OUT: (\/\/\/\/COMMENT-OUT: " + "user_pref[ \t]*\\([ \t]*[\"']" + RegExp.escape(prefName) + "[\"'])", "gm"); output_box.value = output_box.value.replace(x, "$1"); } // if override tagging is required, for active overrides user_pref // prefix any already occurring same name user_pref with ////OVERRIDE: x = new RegExp("^[ \t]*user_pref", "i"); if ( (override_tagging_on) && (x.test(line)) ) { // get prefname x = new RegExp("^(.*user_pref)" // $1 user_pref //user_pref etc + "([ \t]*\\([ \t]*[\"'])" // $2 (" (' + "([^\"']+)" // $3 prefname + "([\"'][ \t]*,[ \t]*)" // $4 ", ', + "(.*)" // $5 prefvalue "prefvalue" 'prefvalue' + "([ \t]*\\)[ \t]*;)" // $6 ); + "(.*)$", "gi"); // $7 afters/comment prefName = line.replace(x,"$3"); // override comment pref in the output if (prefName != "_user.js.parrot") { x = new RegExp("user_pref[ \t]*\\([ \t]*[\"']" + RegExp.escape(prefName) + "[\"']", "gm"); output_box.value = output_box.value.replace(x, "\/\/\/\/OVERRIDE: $&"); // prevent duplicated ////OVERRIDE: x = new RegExp("\/\/\/\/OVERRIDE: (\/\/\/\/OVERRIDE: " + "user_pref[ \t]*\\([ \t]*[\"']" + RegExp.escape(prefName) + "[\"'])", "gm"); output_box.value = output_box.value.replace(x, "$1"); } } // append line output_box.value += line + "\n"; } append_content = []; output_box.value += "\n\n\n\n/*** (overrides) (end of overrides) ***/\n"; output_box.value += 'user_pref("_user.js.parrot", "(overrides) (end of overrides)' + ': SUCCESS");' + "\n"; } /* end function userjsAppend */ // ************************************* // userjsReduce // ************************************* function userjsReduce(input_box_name, output_box_name, skeleton) { var input_box = document.getElementById(input_box_name); var output_box = document.getElementById(output_box_name); output_box.value = ''; // convert in-block comments to in-line (improves inactive pref recognition) var input_content = amendCodeComments(input_box.value, true) .replace(/(\r\n|\r)/g,'\n').split("\n"); var section_number_tag = "0000"; var section_number_tag_9999_prefix = ""; var userjs_type = ""; updateDateTimeStampVariable(); if (skeleton) { output_box.value += "// " + date_time_stamp + " skeleton\n\n"; output_box.value += "/*** (overrides) (TOP) / Introduction ***/"; } else { output_box.value += "// " + date_time_stamp + " reduced\n"; } var i = 0; for (i in input_content) { var line = input_content[i]; var x="",r=""; if (skeleton) { if (line == "/*** (overrides) (TOP) / Introduction ***/") { continue; } } userjs_type = detectUserjsType(line, userjs_type, i); // pick up the arkenfox/user.js section number if (userjs_type == "arkenfox") { if ( new RegExp("^\/\/ /[*/]+ +(START|END|[0-9][^:]+):").test(line) || new RegExp("^\/\/ /[*/]+ +\\[SECTION [^:]+:").test(line) ) { section_number_tag = line; x = new RegExp("^(?:\/\/ )/[*/]+ +\\[SECTION ([^\\]]+)\\]:.*$"); section_number_tag = section_number_tag.replace(x, "$1"); x = new RegExp("^(?:\/\/ )/[*/]+ +([^:]+):.*$"); section_number_tag = section_number_tag.replace(x, "$1"); if (/^9999/.test(section_number_tag)) { section_number_tag_9999_prefix = '9999:'; } else if (/^END/.test(section_number_tag)) { section_number_tag_9999_prefix = ''; } } } if (skeleton) { // prefix arkenfox/user.js parrot value x = new RegExp("^([ \t/*]*user_pref[ \t]*" + "\\([ \t]*[\"']_user\.js\.parrot[\"'][ \t]*,[ \t]*[\"'])" + "(.*)$"); if (new RegExp("\\(overrides\\)").test(line) == false) { line = line.replace(x, "$1(overrides) $2"); } } // prefix user_pref line with a temporary ##PREF## identifier line = line.replace("\/\/user_pref", "\/\/ user_pref"); line = line.replace("\/\*user_pref", "\/* user_pref"); x = new RegExp( "^[ \t]*" + "(user_pref|\/[\/\*].*user_pref)" + "[ \t]*" + "(\\()" + "[ \t]*" + "([\"'][^\"']+[\"'])" + "[ \t]*,[ \t]*" + "(.*)" + "[ \t]*" + "(\\))" + "[ \t]*" + "(;).*$"); if (skeleton) { // comment the pref // line = line.replace(x, "##PREF##\/\/ $1$2$3, $4$5$6"); } else { line = line.replace(x, "##PREF##$1$2$3, $4$5$6"); } // keep section_heading or pref lines if ( detectSectionHeading(line, userjs_type, i) == true ) { line = tidySectionHeading(line, true); if (skeleton) { // output heading line (with '(overrides)' inserted) line = line.replace(new RegExp("^\\(overrides\\) "), ""); output_box.value += "\n\n/*** (overrides) " + line + " ***/"; } else { output_box.value += "\n\n/*** " + line + " ***/"; } } else if (/^##PREF##/.test(line)) { // remove the prefix line = line.replace(new RegExp("^##PREF##"), ""); if (skeleton) { // un-comment the arkenfox/user.js parrot x = new RegExp("^//[ \t/*]*(user_pref[ \t]*" + "\\([ \t]*[\"']_user\.js\.parrot[\"'][ \t]*,[ \t]*[\"'].*)$"); line = line.replace(x, "$1"); } // output user_pref line (and add section number if arkenfox) output_box.value += "\n" + line; if (userjs_type == "arkenfox") { output_box.value += " // " + section_number_tag_9999_prefix + section_number_tag; } } } /* end for (i in input_content) */ input_content = []; output_box.value += "\n"; } /* end function userjsReduce */ // ************************************* // userjsToValueGroups // ************************************* function userjsToValueGroups(input_box_name, output_box_name) { var input_box = document.getElementById(input_box_name); var output_box = document.getElementById(output_box_name); output_box.value = ''; var prefArray = []; // each id in valueGroups gets unique incrementing index var valueGroups = { 'bfa': { 'id': 0, 'count': 0, 'name': "active boolean false" }, 'bta': { 'id': 0, 'count': 0, 'name': "active boolean true" }, 'i0a': { 'id': 0, 'count': 0, 'name': "active integer 0" }, 'i1a': { 'id': 0, 'count': 0, 'name': "active integer 1" }, 'i2a': { 'id': 0, 'count': 0, 'name': "active integer 2" }, // 'i3a': { 'id': 0, 'count': 0, 'name': "active integer 3" }, 'ixa': { 'id': 0, 'count': 0, 'name': "active integer other" }, 'sea': { 'id': 0, 'count': 0, 'name': "active string empty" }, 'sxa': { 'id': 0, 'count': 0, 'name': "active string other" }, 'zza': { 'id': 0, 'count': 0, 'name': "active invalid" }, 'bfi': { 'id': 0, 'count': 0, 'name': "inactive boolean false" }, 'bti': { 'id': 0, 'count': 0, 'name': "inactive boolean true" }, 'i0i': { 'id': 0, 'count': 0, 'name': "inactive integer 0" }, 'i1i': { 'id': 0, 'count': 0, 'name': "inactive integer 1" }, 'i2i': { 'id': 0, 'count': 0, 'name': "inactive integer 2" }, // 'i3i': { 'id': 0, 'count': 0, 'name': "inactive integer 3" }, 'ixi': { 'id': 0, 'count': 0, 'name': "inactive integer other" }, 'sei': { 'id': 0, 'count': 0, 'name': "inactive string empty" }, 'sxi': { 'id': 0, 'count': 0, 'name': "inactive string other" }, 'zzi': { 'id': 0, 'count': 0, 'name': "inactive invalid" } } var idinc=1; Object.keys(valueGroups).forEach(function(key){ valueGroups[key].id = idinc++; }); // add dividers to prefArray Object.keys(valueGroups).forEach(function(key){ if (valueGroups[key].id > 0) { prefArray.push( { 'group': valueGroups[key].id, 'name': "", 'state': "-", 'value': "-", 'line': "", 'hidden': false, 'count': 0 } ); } }); var user_pref_regex = new RegExp( "^([ \t]*user_pref|.*//.*user_pref)" // $1 user_pref //user_pref etc + "([ \t]*\\([ \t]*[\"'])" // $2 (" (' + "([^\"']+)" // $3 prefname + "([\"'][ \t]*,[ \t]*)" // $4 ", ', + "(.*)" // $5 prefvalue "prefvalue" 'prefvalue' + "([ \t]*\\)[ \t]*;)" // $6 ); + "(.*)$", "gi" // $7 afters/comment ); var line, prefState, prefName, prefValue, prefComment, found, ic = 0; // convert in-block comments to in-line (improves inactive pref recognition) var input_content = amendCodeComments(input_box.value, true) .replace(/(\r\n|\r)/g,'\n').split("\n"); // read user_pref into prefArray for (ic in input_content) { line = input_content[ic]; if (user_pref_regex.test(line)) { prefState = line.replace(user_pref_regex, "$1"); prefName = line.replace(user_pref_regex, "$3"); prefValue = line.replace(user_pref_regex, "$5"); prefComment = line.replace(user_pref_regex, "$7"); if (new RegExp("^.*[/][/*].*user_pref", "i").test(prefState)) { prefState = "//"; } else { prefState = ""; } // check if already in the results array found=-1; for (var j = 0, len = prefArray.length; j < len; j++) { if (prefArray[j].name == prefName) { found = j; break; } } // update or add to the results array if (found > -1) { // new pref replaces previous if new=active or previous=inactive if ( (prefState == '') || (prefArray[found].state != '') ) { prefArray[found].state = prefState; prefArray[found].value = prefValue; prefArray[found].line = line; if (/hidden/i.test(prefComment)) { prefArray[found].hidden = true; } // and append ////{#count: previous} // prefArray[found].line = // line + ' ////{#' + prefArray[found].count + ': ' // + prefArray[found].line // .replace(/^(.*)user_pref\("[^"]+", *(.*)\);(.*)/, "$1$2$3") // + '}'; } prefArray[found].count ++; } else { // add prefArray.push( { 'group': 0, 'name': prefName, 'state': prefState, 'value': prefValue, 'line': line, 'hidden': (/hidden/i.test(prefComment) ? true : false), 'count': 1, } ); } } } input_content = []; // allocate value groups (and add count to line end if >1) var groupBase = "", groupSuffix = ""; for (var i = 0, len = prefArray.length; i < len; i++) { if (prefArray[i].state != "-") { if (prefArray[i].state == '//') { groupSuffix = "i" } else { groupSuffix = "a" } if (prefArray[i].value == "false") { groupBase = "bf"; } else if (prefArray[i].value == "true") { groupBase = "bt"; } else if (prefArray[i].value == "0") { groupBase = "i0"; } else if (prefArray[i].value == "1") { groupBase = "i1"; } else if (prefArray[i].value == "2") { groupBase = "i2"; } // else if (prefArray[i].value == "3") { groupBase = "i3"; } else if (new RegExp("^[0-9.+-]+$").test(prefArray[i].value)) { groupBase = "ix"; // integer other } else if (new RegExp("^[\"'][\"']$").test(prefArray[i].value)) { groupBase = "se"; // string empty } else if (new RegExp("^[\"'].*[\"']$").test(prefArray[i].value)) { groupBase = "sx"; // string other } else { groupBase = "zz"; // invalid } valueGroups[groupBase + groupSuffix].count++; prefArray[i].group = valueGroups[groupBase + groupSuffix].id; } // add count and hidden for multiple occurance if (prefArray[i].count > 1) { prefArray[i].line += ' //// (x' + prefArray[i].count + (prefArray[i].hidden ? " [HIDDEN PREF]?" : "") + ')'; } } // sort prefArray.sort((a, b) => a.name.localeCompare(b.name)); prefArray.sort((a, b) => a.group - b.group); // output updateDateTimeStampVariable(); var activeTitleNeeded = true, inactiveTitleNeeded = true; for (var i = 0, len = prefArray.length; i < len; i++) { if (prefArray[i].state == "-") { // output title Object.keys(valueGroups).forEach(function(key){ // find the valueGroups that matches the title if (prefArray[i].group == valueGroups[key].id) { // output active/inactive heading (if not already done) if ( (activeTitleNeeded) && (/^active/.test(valueGroups[key].name)) ) { output_box.value = "/*** __________ " + date_time_stamp + " active user_pref" + " grouped by values (no repeats) __________ ***/\n"; activeTitleNeeded = false; } if ( (inactiveTitleNeeded) && (/^inactive/.test(valueGroups[key].name)) ) { output_box.value += "\n/*** __________ " + date_time_stamp + " inactive user_pref" + " grouped by values (no repeats) __________ ***/\n"; inactiveTitleNeeded = false; } // output title output_box.value += "\n/*** " + valueGroups[key].name + " (" + valueGroups[key].count + ") ***/\n" } }); } else { // output user_pref output_box.value += prefArray[i].line +"\n" } } } /* end function userjsToValueGroups */ // ************************************* // userjsCompare // ************************************* function userjsCompare(input_box_name, input_box_name_2, show, layout) { var input_box = document.getElementById(input_box_name); var input_box_2 = document.getElementById(input_box_name_2); // eg "box_1" instead of "box_1_template" var input_box_short_name = input_box_name.replace(/_[a-z]+$/, ""); var input_box_short_name_2 = input_box_name_2.replace(/_[a-z]+$/, ""); if (input_box_name == input_box_name_2) { input_box_short_name_2 = "n/a"; } var theme = document.body.className.replace( /(^| *)[^_]+_/ , ''); var content_html = "" + '<div id="compare_buttons_bar">' + '<button type="button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="compare_groupx6_button" ' + 'title="Group by value/state">Group x6</button>' + '<button type="button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="compare_groupx12_button" ' + 'title="Group by value/state/active/inactive">Group x12</button>' + '<button type="button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="compare_multiple_button" ' + 'title="Group by multiple occurrence/alphabetical">Multiple</button>' + '<button type="button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="compare_az_button" ' + 'title="Group by alphabetical">A-Z</button>' + '<button type="button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="compare_unsorted_button" ' + 'title="Group by order found">Unsorted</button>' + '<button type="button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="compare_diffstring_button" ' + 'title="Show string difference">DiffStr</button>' + '<button type="button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="compare_layout_button" ' + 'title="" style="margin-right:0.5em;">Layout</button>' + '<br>' // ref: http://www.amp-what.com/unicode/search/triangle + '<button type="button" id="jumpback_button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" style="width:1.9em;min-width:1.9em;" ' + 'title="Jump back a section">▲</button>' + '<button type="button" id="jumpnext_button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" style="width:1.9em;min-width:1.9em;margin-right:0.5em;" ' + 'title="Jump to next section">▼</button>' + '<input type="range" id="viewer_slider" min="0" max="50" value="0">' + '</div>' + '<div style="width: 100%;" id="compare_div">' + '<br><br><br><br>Compare '; switch (show) { case "groupx6": content_html += "[Group x6] (value/state)"; break; case "groupx12": content_html += "[Group x12] (value/state/active/inactive)"; break; case "multiple": content_html += "[Multiple] (multiple occurrence/alphabetical)"; break; case "az": content_html += "[A-Z] (alphabetical)"; break; case "unsorted": content_html += "[Unsorted] (order found)"; break; case "diffstring": content_html += "[DiffStr] (string difference)" + "<br><br> (Uses a 3rd party library, see Help/Acknowledgments)" + "<br> (It is better to use file comparison software, eg meld)" + '<br><br>(<ins>1st</ins>: ' + input_box_short_name + ')' + ' (<del>2nd</del>: ' + input_box_short_name_2 + ')<hr>'; break; default: show = "az"; content_html += "[A-Z] (alphabetical)"; } if (show != "diffstring") { switch (layout) { case "5column": content_html += ' [Layout: 5 column]<br><br>'; break; case "3column": content_html += ' [Layout: 3 column]<br><br>'; break; case "2column": content_html += ' [Layout: 2 column]<br><br>'; break; default: layout = "5column"; content_html += ' [Layout: 5 column]<br><br>'; } } var index_select_html = '' + '<option value="" disabled selected hidden>▾Index</option>\n' + '<option value="0">(TOP)</option>\n'; var groups_container_html = ''; var sectionCount = 0; if (show == "diffstring") { //////////////////////////////////////// // diffString (userjsCompare) //////////////////////////////////////// sectionCount++; // diffString(old, new) content_html += '<span class="anchor" id="1"></span>' + '<div id=diffstr_area>' + escapeHtml(diffString(input_box_2.value, input_box.value)) .replace(/<del>/g, "<del>") .replace(/<\/del>/g, "</del>") .replace(/<ins>/g, "<ins>") .replace(/<\/ins>/g, "</ins>") + '</div>' + '<span class="anchor" id="2"></span></div>\n'; sectionCount++; } else { //////////////////////////////////////// // read content into an array (userjsCompare) //////////////////////////////////////// var prefArray = []; // id values get dynamically set after this variable // (then id > 0 will have sub-sections/links) // id defaults for groupx12 view (-1 is no sub-section) // id value of 0 will be replaced with unique incremental number var stats = { 'total': { 'id': -2, 'sub': false, 'count': 0, 'name': "Combined total" }, 'multiple1+2': { 'id': -2, 'sub': true, 'count': 0, 'name': "Multiple occurrence in 1st and 2nd" }, 'multiple1': { 'id': -2, 'sub': true, 'count': 0, 'name': "Multiple occurrence in 1st (single or not in 2nd)" }, 'multiple2': { 'id': -2, 'sub': true, 'count': 0, 'name': "Multiple occurrence in 2nd (single or not in 1st)" }, 'nomultiple1+2': { 'id': -2, 'sub': true, 'count': 0, 'name': "Remainder (without multiples) in 1st and 2nd" }, 'nomultiple1': { 'id': -2, 'sub': true, 'count': 0, 'name': "Remainder (without multiples) in 1st" }, 'nomultiple2': { 'id': -2, 'sub': true, 'count': 0, 'name': "Remainder (without multiples) in 2nd" }, 'total1': { 'id': -2, 'sub': false, 'count': 0, 'name': "In 1st" }, 'total1a': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 1st - active " }, 'total1i': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 1st - inactive" }, 'total1m': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 1st & multiple occurrence" }, 'total1s': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 1st & single occurrence" }, 'total2': { 'id': -2, 'sub': false, 'count': 0, 'name': "In 2nd" }, 'total2a': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 2nd - active" }, 'total2i': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 2nd - inactive" }, 'total2m': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 2nd & multiple occurrence" }, 'total2s': { 'id': -2, 'sub': true, 'count': 0, 'name': "In 2nd & single occurrence" }, 'match+': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:match & State:match" }, 'match+a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match & State:match - active" }, 'match+i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match & State:match - inactive" }, 'match-': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:match & State:differ" }, 'match-a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match & State:differ - active in 1st" }, 'match-i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match & State:differ - inactive in 1st" }, 'differ+': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:differ & State:match" }, 'differ+a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ & State:match - active" }, 'differ+i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ & State:match - inactive" }, 'differ-': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:differ & State:differ" }, 'differ-a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ & State:differ - active in 1st" }, 'differ-i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ & State:differ - inactive in 1st" }, 'only1': { 'id': -1, 'sub': false, 'count': 0, 'name': "Only in 1st" }, 'only1a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Only in 1st - active" }, 'only1i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Only in 1st - inactive" }, 'only2': { 'id': -1, 'sub': false, 'count': 0, 'name': "Only in 2nd" }, 'only2a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Only in 2nd - active" }, 'only2i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Only in 2nd - inactive" } } // set id for sub-sections we want (depending on view required) if (show == "groupx12") { // want those with default id of 0 var idinc=1; Object.keys(stats).forEach(function(key){ if (stats[key].id == 0) { stats[key].id = idinc++; } }); } else if (show == "groupx6") { // want those with default id of -1 var idinc=1; Object.keys(stats).forEach(function(key){ if (stats[key].id == -1) { stats[key].id = idinc++; } else { stats[key].id = -1; } }); } else if (show == "unsorted") { // first set all -1 (off), then index what we need var idinc=1; Object.keys(stats).forEach(function(key){ if (stats[key].id == 0) { stats[key].id = -1; } if (key == "total1" || key == "only2") { stats[key].id = idinc++; } }); } else if (show == "multiple") { // first set all -1 (off), then index what we need var idinc=1; Object.keys(stats).forEach(function(key){ if (stats[key].id == 0) { stats[key].id = -1; } if ( key == "multiple1+2" || key == "multiple1" || key == "multiple2" || key == "nomultiple1+2" || key == "nomultiple1" || key == "nomultiple2" ) { stats[key].id = idinc++; } }); } else { // a-z // first set all -1 (off), then index what we need var idinc=1; Object.keys(stats).forEach(function(key){ if (stats[key].id == 0) { stats[key].id = -1; } if ( key == "total" ) { stats[key].id = idinc++; } }); } // add dividers to prefArray Object.keys(stats).forEach(function(key){ if (stats[key].id > 0) { prefArray.push( { 'diff': stats[key].id, 'name': "", 'state1': "-", 'value1': "-", 'count1': 0, 'state2': "-", 'value2': "-", 'count2': 0 } ); } }); var user_pref_regex = new RegExp( "^([ \t]*user_pref|.*//.*user_pref)" // $1 user_pref //user_pref etc + "([ \t]*\\([ \t]*[\"'])" // $2 (" (' + "([^\"']+)" // $3 prefname + "([\"'][ \t]*,[ \t]*)" // $4 ", ', + "(.*)" // $5 prefvalue "prefvalue" 'prefvalue' + "([ \t]*\\)[ \t]*;)" // $6 ); + "(.*)$", "gi" // $7 afters/comment ); var line, prefState, prefName, prefValue, found, ic = 0; //////////////////////////////////////// // add 1st input to prefArray (userjsCompare) //////////////////////////////////////// // convert in-block comments to in-line (improves inactive pref recognition) var input_content = amendCodeComments(input_box.value, true) .replace(/(\r\n|\r)/g,'\n').split("\n"); for (ic in input_content) { line = input_content[ic]; if (user_pref_regex.test(line)) { prefState = line.replace(user_pref_regex, "$1"); prefName = line.replace(user_pref_regex, "$3"); prefValue = line.replace(user_pref_regex,"$5"); if (new RegExp("^.*[/][/*].*user_pref", "i").test(prefState)) { prefState = "//"; } else { prefState = ""; } // check if already in the results array found=-1; for (var j = 0, len = prefArray.length; j < len; j++) { if (prefArray[j].name == prefName) { found = j; break; } } // update or add to the results array if (found > -1) { // new pref replaces previous if new=active or previous=inactive if ( (prefState == '') || (prefArray[found].state1 != '') ) { prefArray[found].state1 = prefState; prefArray[found].value1 = prefValue; } prefArray[found].count1 ++; } else { // add prefArray.push( { 'name': prefName, 'diff': 0, 'state1': prefState, 'value1': prefValue, 'count1': 1, 'state2': "-", 'value2': "-", 'count2': 0 } ); } } } input_content = []; //////////////////////////////////////// // add 2nd input to prefArray (userjsCompare) //////////////////////////////////////// if (input_box_name == input_box_name_2) { // just viewing stats etc on one box input_content = []; } else { // convert in-block comments to in-line (improves inactive pref recognition) input_content = amendCodeComments(input_box_2.value, true) .replace(/(\r\n|\r)/g,'\n').split("\n"); } for (ic in input_content) { line = input_content[ic]; if (user_pref_regex.test(line)) { prefState = line.replace(user_pref_regex, "$1"); prefName = line.replace(user_pref_regex, "$3"); prefValue = line.replace(user_pref_regex,"$5"); if (new RegExp("^.*[/][/*].*user_pref", "i").test(prefState)) { prefState = "//"; } else { prefState = ""; } // check if already in the results array found=-1; for (var j = 0, len = prefArray.length; j < len; j++) { if (prefArray[j].name == prefName) { found = j; break; } } // update or add to the results array if (found > -1) { // new pref replaces previous if new=active or previous=inactive if ( (prefState == '') || (prefArray[found].state2 != '') ) { prefArray[found].state2 = prefState; prefArray[found].value2 = prefValue; } prefArray[found].count2 ++; } else { // add prefArray.push( { 'name': prefName, 'diff': 0, 'state1': "-", 'value1': "-", 'count1': 0, 'state2': prefState, 'value2': prefValue, 'count2': 1 } ); } } } input_content = []; //////////////////////////////////////// // calculate: differences and stats (userjsCompare) //////////////////////////////////////// var statBase = "", statSuffix = ""; for (var i = 0, len = prefArray.length; i < len; i++) { stats["total"].count++; statBase = "total1"; stats[statBase].count++; if (prefArray[i].state1 == '') { stats[statBase + "a"].count++; } else if (prefArray[i].state1 == '//') { stats[statBase + "i"].count++; } else { stats[statBase].count--; } if (prefArray[i].state1 != '-' && prefArray[i].count1 > 1) { stats[statBase + "m"].count++; } else if (prefArray[i].state1 != '-') { stats[statBase + "s"].count++; } statBase = "total2"; stats[statBase].count++; if (prefArray[i].state2 == '') { stats[statBase + "a"].count++; } else if (prefArray[i].state2 == '//') { stats[statBase + "i"].count++; } else { stats[statBase].count--; } if (prefArray[i].state2 != '-' && prefArray[i].count2 > 1) { stats[statBase + "m"].count++; } else if (prefArray[i].state2 != '-') { stats[statBase + "s"].count++; } // title if (prefArray[i].state1 == "-" && prefArray[i].state2 == "-" ) { stats["total"].count--; } else { // present in 1 only if (prefArray[i].state2 == "-") { statBase = "only1"; if (prefArray[i].state1 == '//') { statSuffix = "i" } else { statSuffix = "a" } // inc count stats[statBase].count++; stats[statBase + statSuffix].count++; prefArray[i].diff = stats[statBase + statSuffix].id; // adjust for these if (show == "unsorted") { prefArray[i].diff = stats["total1"].id; } else if (show == "groupx6") { prefArray[i].diff = stats[statBase].id; } } // present in 2 only else if (prefArray[i].state1 == "-") { statBase = "only2"; if (prefArray[i].state2 == '//') { statSuffix = "i" } else { statSuffix = "a" } // inc count stats[statBase].count++; stats[statBase + statSuffix].count++; prefArray[i].diff = stats[statBase + statSuffix].id; // adjust for these if (show == "unsorted" || show == "groupx6") { prefArray[i].diff = stats[statBase].id; } } // present in both else { // note value: match or differ if ( prefArray[i].value1 == prefArray[i].value2 ) { statBase = "match"; } else { statBase = "differ" } // note state: same (+) opposite (-) if (prefArray[i].state1 == prefArray[i].state2) { statBase += "+"; } else { statBase += "-"; } // note state: inactive or active if (prefArray[i].state1 == '//') { statSuffix = "i" } else { statSuffix = "a" } // inc count stats[statBase].count++; stats[statBase + statSuffix].count++; prefArray[i].diff = stats[statBase + statSuffix].id; // adjust for these if (show == "unsorted") { prefArray[i].diff = stats["total1"].id; } else if (show == "groupx6") { prefArray[i].diff = stats[statBase].id; } } // multiple or no multiple occurrence if (prefArray[i].count1 > 1 || prefArray[i].count2 > 1) { statBase = "multiple" if (prefArray[i].count1 > 1 && prefArray[i].count2 > 1) { statSuffix = "1+2"; } else if (prefArray[i].count1 > 1) { statSuffix = "1"; } else { statSuffix = "2"; } } else { statBase = "nomultiple" if (prefArray[i].count1 == 1 && prefArray[i].count2 == 1) { statSuffix = "1+2"; } else if (prefArray[i].count1 == 1) { statSuffix = "1"; } else { statSuffix = "2"; } } stats[statBase + statSuffix].count++; if (show == "multiple") { prefArray[i].diff = stats[statBase + statSuffix].id; } if (show == "az") { prefArray[i].diff = stats["total"].id } } } //////////////////////////////////////// // sort (userjsCompare) //////////////////////////////////////// if (show != "unsorted") { prefArray.sort((a, b) => a.name.localeCompare(b.name)); } if (show != "az") { prefArray.sort((a, b) => a.diff - b.diff); } //////////////////////////////////////// // add stats totals table (userjsCompare) //////////////////////////////////////// content_html += '<details><summary>Summary' + ' (1st: ' + input_box_short_name + ')' + ' (2nd: ' + input_box_short_name_2 + ')' + '</summary><br><table id="table_stats">' + '<colgroup><col width="10%" /><col width="10%" />' + '<col width="80%"/> </colgroup><tbody>'; Object.keys(stats).forEach(function(key){ content_html += '<tr><td class="td1_stats_count">'; if (stats[key].sub) { // col1:empty col2:stat col3:name content_html += '</td><td class="td2_stats_count">'; content_html += stats[key].count; content_html += '</td><td class="td3a_stats_name">'; } else { // col1:stat col2+col3:name content_html += stats[key].count; content_html += '</td><td class="td3b_stats_name" colspan="2">'; } if ( stats[key].id > 0 ) { // stat name as a link content_html += '<span class="anav http" id="#' + stats[key].id + '">'; content_html += stats[key].name; content_html += ' [' + stats[key].id + ']' + '</span>'; // add to the index_select button index_select_html += '<option value="' + stats[key].id + '">[' + stats[key].id + '] ' + stats[key].name + ' (' + stats[key].count + ')</option>\n'; } else { // stat name as plain (no link) content_html += stats[key].name; } content_html += '</td></tr>'; }); content_html += "</tbody></table></details>"; //////////////////////////////////////// // add results table (userjsCompare) //////////////////////////////////////// content_html += '<br><table id="table_comp">'; // start: header row content_html += '<tr>'; // 5col 3col (prefname col) if (layout != "2column") { content_html += '<td class="td_comp '; if (layout == "5column") { content_html += 'td1a_comp_name"'; } else if (layout == "3column") { content_html += 'td1b_comp_name" rowspan="2"'; } // add prefname column header content_html += '>Preference Name<br>(Total: ' + stats["total"].count + ')</td>'; } if (layout == "2column") { var td3td5 = 'td3td5_b_comp_value'; } else { var td3td5 = 'td3td5_comp_value' } // 5col 3col 2col (state1 value1 cols) content_html += '<td class="td_comp td2td4_comp_state">State1</td>' + '<td class="td_comp ' + td3td5 + '">Value1<br>(' + input_box_short_name + ': ' + stats["total1"].count + ')</td>'; // 3col 2col (split into 2nd row) if (layout != "5column") { content_html += '</tr><tr>' } // 5col 3col 2col (state2 value2 cols) content_html += '<td class="td_comp td2td4_comp_state">State2</td>' + '<td class="td_comp ' + td3td5 + '">Value2<br>(' + input_box_short_name_2 + ': ' + stats["total2"].count + ')</td></tr>'; // end: header row // each row var titlebm; var diff_state1, diff_value1, diff_state2, diff_value2; var section_heading = '(TOP)'; var group_user_pref_list = ''; groups_container_html += '<a target="_blank" href="about:config?filter=/^\\*$|^('; for (var i = 0, len = prefArray.length; i < len; i++) { diff_state1 = " td_comp_diff"; diff_value1 = " td_comp_diff"; diff_state2 = " td_comp_diff"; diff_value2 = " td_comp_diff"; if (prefArray[i].state1 == prefArray[i].state2) { diff_state1 = ""; diff_state2 = ""; } if ( prefArray[i].value1 == prefArray[i].value2) { diff_value1 = ""; diff_value2 = ""; } if (prefArray[i].state1 == "-") { diff_state1 = ""; } if (prefArray[i].state2 == "-") { diff_state2 = ""; } if (prefArray[i].value1 == "-") { diff_value1 = ""; } if (prefArray[i].value2 == "-") { diff_value2 = ""; } // row/cell contents if (prefArray[i].state1 == "-" && prefArray[i].state2 == "-") { //////////////////////// // section heading row //////////////////////// sectionCount++; // name (title) content_html += '<tr><td class="td_comp '; if (layout == "5column") { content_html += 'td1a_comp_name" colspan="5">'; } else if (layout == "3column") { content_html += 'td1b_comp_name td_comp_dashed" colspan="3">'; } else if (layout == "2column") { content_html += 'td1b_comp_name td_comp_dashed" colspan="2">'; } // anchor content_html += '<br><span class="anchor" id="' + sectionCount + '"></span>'; // add the title info // go through the stats type object // and find the one that matches the diff Object.keys(stats).forEach(function(key){ if (prefArray[i].diff == stats[key].id) { // add: [#] name (count) content_html += '<span class="anav http" id="#' + prefArray[i].diff + '">[' + prefArray[i].diff + "]</span>" + " <b>" + stats[key].name + "</b>" + " (" + stats[key].count + ")<br><br>"; // note name for [Groups] bookmark: [#] name (count) titlebm = "[" + prefArray[i].diff + "] " + stats[key].name + " (" + stats[key].count + ")"; } }); content_html += '</td>'; // end and start about:config bookmarks groups group_user_pref_list = group_user_pref_list.replace(/\|$/, ''); groups_container_html += group_user_pref_list + ')(;|$)|^$/i">-' + section_heading + '</A><br>\n'; group_user_pref_list = ''; section_heading = titlebm; groups_container_html += '<a target="_blank" href="about:config?filter=/^\\*$|^('; } else { ///////////////// // user_pref row ///////////////// // pref name content_html += '<tr>'; // 5col 3col (prefname col) if (layout != "2column") { content_html += '<td class="td_comp ' if (layout == "5column") { content_html += 'td1a_comp_name">'; } else if (layout == "3column") { content_html += 'td1b_comp_name td_comp_dashed" rowspan="2">'; } // add prefname content_html += "<a target=\"_blank\" href=\"about:config?filter=/^\\*\$|^(" + prefArray[i].name.replace(/([*.+])/g, "\\$1") + ")(;|\$)|^\$/i\">" + prefArray[i].name + "</a>"; // if multiples show the count if ( prefArray[i].count1 > 1 || prefArray[i].count2 > 1) { content_html += ' (x' + prefArray[i].count1 + ' x' + prefArray[i].count2 + ')</td>'; } } group_user_pref_list += prefArray[i].name.replace(/([*.+])/g, "\\$1") + "|"; // 5col 3col 2col (state1 col) content_html += '<td class="td_comp td2td4_comp_state'; if (layout != "5column") { content_html += ' td_comp_dashed'; } content_html += diff_state1 + '">' + '<span class="pref">' + prefArray[i].state1 + '</span>' + '</td>'; // 5col 3col 2col (value1 col) content_html += '<td class="td_comp ' + td3td5; if (layout != "5column") { content_html += ' td_comp_dashed'; } content_html += diff_value1 + '">'; if ((layout == "2column") && (prefArray[i].value1 != "-")) { // add pref name content_html += 'user_pref("' + "<a target=\"_blank\" href=\"about:config?filter=/^\\*\$|^(" + prefArray[i].name.replace(/([*.+])/g, "\\$1") + ")(;|\$)|^\$/i\">" + prefArray[i].name + "</a>" + '", '; } // add value1 content_html += '<span class="' + prefArray[i].value1.replace(/^-$/, "pref") .replace(/^[0-9.+-]+$/, "integer") .replace(/^(".*"|'.*')$/, "string") + '">' + prefArray[i].value1 + '</span>'; if ((layout == "2column") && (prefArray[i].value1 != "-")) { content_html += ');'; } content_html += '</td>'; if (layout != "5column") { content_html += '</tr>' + '<tr>'; } // state2 content_html += '<td class="td_comp td2td4_comp_state' + diff_state2 + '">' + '<span class="pref">' + prefArray[i].state2 + '</span>' + '</td>'; // value2 content_html += '<td class="td_comp ' + td3td5 + diff_value2 + '">'; if ((layout == "2column") && (prefArray[i].value2 != "-")) { // add pref name content_html += 'user_pref("' + "<a target=\"_blank\" href=\"about:config?filter=/^\\*\$|^(" + prefArray[i].name.replace(/([*.+])/g, "\\$1") + ")(;|\$)|^\$/i\">" + prefArray[i].name + "</a>" + '", '; } // add value2 content_html += '<span class="' + prefArray[i].value2.replace(/^-$/, "pref") .replace(/^[0-9.+-]+$/, "integer") .replace(/^(".*"|'.*')$/, "string") + '">' + prefArray[i].value2 + '</span>'; if ((layout == "2column") && (prefArray[i].value2 != "-")) { content_html += ');'; } content_html += '</td>'; } content_html += '</tr>'; } sectionCount++; content_html += '</table>' + '<span class="anchor" id="' + sectionCount + '"></span>' + '</div>'; // end about:config bookmarks groups group_user_pref_list = group_user_pref_list.replace(/\|$/, ''); groups_container_html += group_user_pref_list + ')(;|$)|^$/i">-' + section_heading + '</A><br>\n'; } //////////////////////////////////////// // end content (userjsCompare) //////////////////////////////////////// content_html += ' <br><br><br>\n'; index_select_html += '<option value="' + sectionCount + '">(BOTTOM)</option>\n'; document.getElementById("view_area").innerHTML = content_html; document.getElementById("index_select").innerHTML = index_select_html; // also remove first group if unused document.getElementById("groups_container").innerHTML = groups_container_html.replace(new RegExp('<a target="_blank"' + ' href="about:config\\?filter=' + '\\/\\^\\\\\\*\\$\\|\\^\\(\\)\\(;\\|\\$\\)\\|\\^\\$\\/i">' + '-\\(TOP\\)<\\/A><br>'), ""); content_html = null; index_select_html = null; groups_container_html = null; scroll(0,0); document.getElementById("compare_" + show + "_button").style.borderWidth = '4px'; document.getElementById("compare_" + show + "_button").style.fontWeight = 'bold'; if (show == "diffstring") { // add class and invert <ins> diffs (leave <del> as strike through) var e = document.getElementsByTagName("ins"); for (var i=0,j=e.length;i<j;i++) { changeClass(e[i], "", "invert"); } invertColorForClass("invert"); // hide layout button document.getElementById("compare_layout_button").style.display = "none"; } document.getElementById("compare_unsorted_button").addEventListener("click", function() { userjsCompare(input_box_name, input_box_name_2, "unsorted", layout); }); document.getElementById("compare_multiple_button").addEventListener("click", function() { userjsCompare(input_box_name, input_box_name_2, "multiple", layout); }); document.getElementById("compare_az_button").addEventListener("click", function() { userjsCompare(input_box_name, input_box_name_2, "az", layout); }); document.getElementById("compare_groupx6_button").addEventListener("click", function() { userjsCompare(input_box_name, input_box_name_2, "groupx6", layout); }); document.getElementById("compare_groupx12_button").addEventListener("click", function() { userjsCompare(input_box_name, input_box_name_2, "groupx12", layout); }); document.getElementById("compare_diffstring_button").addEventListener("click", function() { userjsCompare(input_box_name, input_box_name_2, "diffstring", layout); }); document.getElementById("compare_layout_button").addEventListener("click", function() { if (layout == "5column") { layout = "3column" } else if (layout == "3column") { layout = "2column" } else { layout = "5column" } userjsCompare(input_box_name, input_box_name_2, show, layout); }); // back document.getElementById("jumpback_button").addEventListener("click", function() { var e = document.getElementsByClassName("anchor"); var id = 0; for (var i = 0, j = e.length; i<j; i++) { if (e[i].getBoundingClientRect().y >= 0) { break; } else { id = i + 1; } } document.getElementById("viewer_slider").value = id; if (id == 0) { scroll(0,0); } else { document.getElementById(id).scrollIntoView(); } }); // next document.getElementById("jumpnext_button").addEventListener("click", function() { var e = document.getElementsByClassName("anchor"); var id = 1; for (var i = 0, j = e.length; i<j; i++) { if (e[i].getBoundingClientRect().y > 1) { break; } else { id = i + 2; } } document.getElementById("viewer_slider").value = id; if (id == 0) { scroll(0,0); } else { document.getElementById(id).scrollIntoView(); } }); // slider document.getElementById("viewer_slider").max = sectionCount; for (const event of [ "input", "click" ]) { document.getElementById("viewer_slider").addEventListener(event, function() { if (this.value == 0) { scroll(0,0); } else { document.getElementById(this.value).scrollIntoView(); } }); } // when the back next symbols are clicked update the nav slider var e = document.getElementsByClassName("anav"); for (var i = 0, j = e.length; i<j; i++) { e[i].addEventListener("click", function() { var id = this.id.replace(/^.*#$/, "0").replace(/^.*#/, ""); document.getElementById("viewer_slider").value = id; if (id == 0) { scroll(0,0); } else { document.getElementById(id).scrollIntoView(); } }); } togglePrefixAboutConfigLinks("refresh"); if (toggleGroupsOnView("status")) { toggleGroupsPanel(true); } } /* end function userjsCompare */ // ************************************* // userjsCompareLauncher // ************************************* function userjsCompareLauncher(choice) { // choice eg compare:1:4 compare:1:4:az:2column etc // determine the box numbers to compare from the choice text var box1st = choice.replace(/^[^0-9]*([0-9])[^0-9]+([0-9]).*$/, "$1"); var box2nd = choice.replace(/^[^0-9]*([0-9])[^0-9]+([0-9]).*$/, "$2"); // groupx6 groupx12 multiple az unsorted diffstring var show = choice.replace(/^[^0-9]*([0-9])[^0-9]+([0-9]):([^:]+).*$/, "$3"); // 5column 3column 2column var layout = choice.replace(/^[^0-9]*([0-9])[^0-9]+([0-9]):([^:]*):([^:]+).*$/, "$4"); switch (box1st) { case "1": box1st="box_1_template"; break; case "2": box1st="box_2_overrides"; break; case "3": box1st="box_3_userjs"; break; case "4": box1st="box_4_other"; break; } switch (box2nd) { case "1": box2nd="box_1_template"; break; case "2": box2nd="box_2_overrides"; break; case "3": box2nd="box_3_userjs"; break; case "4": box2nd="box_4_other"; break; } if ( document.getElementById("collect_button").style.textDecoration != "line-through" && ( !(document.getElementById(box1st).value == "") || !(document.getElementById(box2nd).value == "") ) ) { if ( (document.getElementById(box1st).value == "") || (document.getElementById(box2nd).value == "") || (box1st == box2nd) ) { // trying to compare with empty/same box (ie just one box) if (!show || show == choice) { show = "az" } } else { if (!show || show == choice) { show = "groupx6" } } if (!layout || layout == choice) { layout = "5column" } userjsCompare(box1st, box2nd, show, layout); toggleActionsPanel(false); } } /* end function userjsCompareLauncher */ // ************************************* // userjsTableView // ************************************* function userjsTableViewExpandAll() { var e = document.getElementsByClassName("det"); for (var i = 0, j = e.length; i < j; i++) { e[i].open = true; } } function userjsTableViewExpandPrefDesc() { var e = document.getElementsByClassName("prefDet"); for (var i = 0, j = e.length; i < j; i++) { e[i].open = true; } } function userjsTableViewCollapseSectionDesc() { var e = document.getElementsByClassName("secDet"); for (var i = 0, j = e.length; i < j; i++) { e[i].open = false; } } function userjsTableViewCollapseAll() { var e = document.getElementsByClassName("det"); for (var i = 0, j = e.length; i < j; i++) { e[i].open = false; } } function userjsTableViewTagFilter() { var s = document.getElementById("filter_select"); var svalue = s.value; s.selectedIndex = 0; var hideheadingsoption = 2; var hideinactiveoption = 3; var hideactiveoption = 4; var invertoption = 5; var e = document.getElementById("table_tview").querySelectorAll("tr"); var group_user_pref_list_filter = ""; // get menu status var hideinactive = /\u{25A3}/u.test(s.options[hideinactiveoption].textContent) ? true : false; var hideactive = /\u{25A3}/u.test(s.options[hideactiveoption].textContent) ? true : false; var hideheadings = /\u{25A3}/u.test(s.options[hideheadingsoption].textContent) ? true : false; var invert = /\u{25A3}/u.test(s.options[invertoption].textContent) ? true : false; var filteron = ""; for (var si = invertoption + 1, sj = s.options.length; si < sj; si++) { if (/\u{25A3}/u.test(s.options[si].textContent)) { filteron = s.options[si].value; } } if (svalue == "SHOWALL") { hideinactive = false; hideactive = false; hideheadings = false; invert = false; filteron = ""; } else if (svalue == "HIDEHEADINGS") { svalue = ""; hideheadings = !hideheadings; } else if (svalue == "HIDEINACTIVE") { svalue = ""; hideinactive = !hideinactive; hideactive = false; } else if (svalue == "HIDEACTIVE") { svalue = ""; hideactive = !hideactive; hideinactive = false; } else if (svalue == "INVERT") { svalue = ""; invert = !invert; } else if (svalue == filteron) { svalue = "SHOWALL"; filteron = ""; hideheadings = false; } else { filteron = svalue; hideheadings = true; } // loop through all tr and apply/re-apply filter for (var i = 1, j = e.length; i < j; i++) { e[i].style.display = (svalue == "SHOWALL" || new RegExp(filteron + " ").test(e[i].className)) ? null : "none"; } // if invert if (invert) { for (var i = 1, j = e.length; i < j; i++) { e[i].style.display = (e[i].style.display == "none") ? null : "none"; } } // if hide inactive if (hideinactive) { for (var i = 1, j = e.length; i < j; i++) { if (/TAGS_Inactive /.test(e[i].className)) { e[i].style.display = "none"; } } } // if hide active if (hideactive) { for (var i = 1, j = e.length; i < j; i++) { if (/TAGS_Active /.test(e[i].className)) { e[i].style.display = "none"; } } } // hide/unhide headings for (var i = 1, j = e.length; i < j; i++) { if (/HEADING /.test(e[i].className)) { e[i].style.display = hideheadings ? "none" : null; } } // mark the filter options on/off on the filter select/button s.options[hideinactiveoption].textContent = hideinactive ? s.options[hideinactiveoption].textContent.replace(/\u{25A2}/u, "\u25A3") : s.options[hideinactiveoption].textContent.replace(/\u{25A3}/u, "\u25A2"); s.options[hideactiveoption].textContent = hideactive ? s.options[hideactiveoption].textContent.replace(/\u{25A2}/u, "\u25A3") : s.options[hideactiveoption].textContent.replace(/\u{25A3}/u, "\u25A2"); s.options[hideheadingsoption].textContent = hideheadings ? s.options[hideheadingsoption].textContent.replace(/\u{25A2}/u, "\u25A3") : s.options[hideheadingsoption].textContent.replace(/\u{25A3}/u, "\u25A2"); s.options[invertoption].textContent = invert ? s.options[invertoption].textContent.replace(/\u{25A2}/u, "\u25A3") : s.options[invertoption].textContent.replace(/\u{25A3}/u, "\u25A2"); // mark the filter used and unmark others (except hide/invert) for (var si = invertoption + 1, sj = s.options.length; si < sj; si++) { s.options[si].textContent = (s.options[si].value == filteron) ? s.options[si].textContent.replace(/\u{25A2}/u, "\u25A3") : s.options[si].textContent.replace(/\u{25A3}/u, "\u25A2"); } // filter button (indicates if any filter is used) s.options[0].textContent = (hideinactive || hideactive || hideheadings || invert || filteron) ? s.options[0].textContent.replace(/\u{25A2}/u, "\u25A3") : s.options[0].textContent.replace(/\u{25A3}/u, "\u25A2"); // update group_link_showing_table_filter (inside groups_container div) var prefnametd; for (var i = 1, j = e.length; i < j; i++) { if (!(e[i].style.display == "none")) { // get prefname from table text (where e[i] is the tr) // TODO check prefnametd = e[i].getElementsByClassName("td_tview_name"); if (prefnametd.length > 0) { if (prefnametd[0].textContent) { group_user_pref_list_filter += prefnametd[0].textContent.replace(/([*.+])/g, "\\$1") + "|"; } } } } document.getElementById("group_link_showing_table_filter").href = 'about:config?filter=/^\\*$|^(' + group_user_pref_list_filter.replace(/\|$/, '') + ')(;|$)|^$/i'; s.blur(); } /* end function userjsTableViewTagFilter */ //////////////////////////////////////////////////////// // userjsTableView //////////////////////////////////////////////////////// function userjsTableView(input_box_name) { var input_box = document.getElementById(input_box_name); var theme = document.body.className.replace( /(^| *)[^_]+_/ , ''); // tags (shown under filter button in this order) // these are used on the table row eg: <tr class="TAGS_WARNING "...> // those below are hardcoded (with an indicator code for the info column) // any other tags will be detected later (and use a * as indicator code) var tags = { // tags to show first 'WARNING': { 'rx': new RegExp("(\\[WARNING\\]|\\[WARNING.)", "gi"), 're': '<span class="warn">$1</span>', 'indicator': '<span class="warn">W</span>', 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'HIDDEN': { 'rx': new RegExp("(\\[HIDDEN[^\\]]*\\])", "gi"), 're': '<span class="hid">$1</span>', 'indicator': '<span class="hid">H</span>', 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'SETUP... (listed below)': { 'rx': new RegExp("(\\[SETUP[^\\]]*\\])", "gi"), 're': '<span class="setup">$1</span>', 'indicator': '<span class="setup">S</span>', 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'DEFAULT': { 'rx': new RegExp("(\\[DEFAULT[^\\]]*\\])", "gi"), 're': "", 'indicator': "D", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, // other tags 'FF... (listed below)': { 'rx': new RegExp("(\\[FF[^\\]]*\\])", "gi"), 're': "", 'indicator': "F", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'NOTE': { 'rx': new RegExp("(\\[NOTE[^\\]]*\\])", "gi"), 're': "", 'indicator': "N", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'RESTART': { 'rx': new RegExp("(\\[RESTART[^\\]]*\\])", "gi"), 're': "", 'indicator': "R", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'SETTING': { 'rx': new RegExp("(\\[SETTING[^\\]]*\\])", "gi"), 're': "", 'indicator': "X", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'TIP': { 'rx': new RegExp("(\\[TIP[^\\]]*\\])", "gi"), 're': "", 'indicator': "I", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, // os 'WINDOWS': { 'rx': new RegExp("(\\[WINDOWS[^\\]]*\\])", "gi"), 're': "", 'indicator': "M", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'NON-WINDOWS': { 'rx': new RegExp("(\\[NON-WINDOWS[^\\]]*\\])", "gi"), 're': "", 'indicator': "O", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'MAC': { 'rx': new RegExp("(\\[MAC[^\\]]*\\])", "gi"), 're': "", 'indicator': "A", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 }, 'LINUX': { 'rx': new RegExp("(\\[LINUX[^\\]]*\\])", "gi"), 're': "", 'indicator': "L", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 } } // check for "[TAGS]" not hardcoded above var extraTags = input_box.value // make [TAGS] be on their own line .replace(/(\[[^\]]*\])/gm,'\n$1\n') .split("\n") // keep just the [TAGS] lines .filter(function(value) { return /^\[.*\]$/.test(value) }) // unique values .filter(function(value, index, self) { return self.indexOf(value) === index }) // prefix [TAGS] for sort priority (SETUP and FF to the end) .map(function(value) { if (new RegExp("^(\\[FF[^\\]]*\\])").test(value) ) { return '3' + value; } else if (new RegExp("^(\\[SETUP[^\\]]*\\])").test(value) ) { return '2' + value; } else { return '1' + value; } }) // case insensitive sort .sort(function (a, b) { var x = a.toLowerCase(), y = b.toLowerCase(); if (x < y) { return -1; } if (x > y) { return 1; } if (a < b) { return -1; } if (a > b) { return 1; } return 0; }) // remove sort priority prefix .map(function(value) { return value.replace(/^./, "") }) // remove non-TAGS eg [SECTION... [0] [a... etc // and remove any already hard coded in tags object above .filter(function(value) { var newtag = true; if (new RegExp("^(\\[SECTION |\\[[^A-Z])").test(value) ) { newtag = false; } else { for (var i in tags) { tags[i].rx.lastIndex = 0; if (i == "SETUP... (listed below)" || i == "FF... (listed below)" ) { // pick up these variations (as we only hardcoded them grouped) continue; } else if (tags[i].rx.test(value)) { // already included (hard coded in tags object above) newtag = false; } } } // keep in extraTags if (newtag) { return value } }); // now include the extra tags for detection (add them to tags object) for (var i in extraTags) { var iv = extraTags[i].replace(/[\[\]]/g, ""); tags[iv] = { 'rx': new RegExp("(\\[" + RegExp.escape(iv) + "\\])", "gi"), 're': "", 'indicator': "*", 'secflag': false, 'subflag': false, 'titleflag': false, 'count': 0 } if (new RegExp("^(SETUP|FF).+").test(iv)) { // remove the * indicator as these TAGS are in a group too tags[iv].indicator = ""; } } var regexes = { 'user_pref': { 'rx': new RegExp( "^([ \t]*user_pref|.*//.*user_pref)" // $1 user_pref //user_pref etc + "([ \t]*\\([ \t]*[\"'])" // $2 (" (' + "([^\"']+)" // $3 prefname + "([\"'][ \t]*,[ \t]*)" // $4 ", ', + "(.*)" // $5 prefvalue "prefvalue" 'prefvalue' + "([ \t]*\\)[ \t]*;)" // $6 ); + "(.*)$", "gi" // $7 afters/comment ) }, 'http': { // linkify a/href for HTTP/HTTPS URLs 'rx': new RegExp("(https?:[/][/][^ \"']+[^\\)\\], \"'.;])", "gi"), 're': '<a target="_blank" rel="external noopener noreferrer" ' + 'href="$1" class="http"><span class="hidden">__</span>$1</A>' } } //////////////////////////////////////// // start parsing (userjsTableView) //////////////////////////////////////// // convert in-block comments to in-line (for inactive pref recognition) var input_content = amendCodeComments(input_box.value, true) .replace(/(\r\n|\r)/g,'\n').split("\n"); var prefArray = []; // create a first section heading prefArray.push( { 'type': "section", 'title': "Introduction", 'desc': "", 'id': "", 'name': "", 'state': "", 'value': "", 'comment': "", 'info': "", 'tagclass': "HEADING " } ); var ic = 0, x, r, line = "", linetype = "", prefid = "", prefid_prefix = "", prefName = "", prefState = "", prefValue = "", prefTitle = "", prefDesc = "", prefComment = "", found, prefInfoSaved = true, // add title+desc as own entry if not put on a pref ISawAParrot = -1, lastSection = 1, secMore = true, // we start in the first description subMore = false, titleMore = false, last_index = (prefArray.length - 1); for (ic in input_content) { line = input_content[ic]; //////////////////////////////////////// // detect the type of line (userjsTableView) //////////////////////////////////////// // section/section+ sub/sub+ title/title+/user_pref regexes["user_pref"].rx.lastIndex = 0; if ( (new RegExp("^// /\\*+[ \\t]*\\[SECTION[ \\t]*[^:]+:").test(line)) || (new RegExp("^//[ *\t]*SECTION:").test(line)) ) { // line starting /*... followed by [SECTION xxxx: // line starting * SECTION: linetype = "section"; lastSection++; } else if ( (new RegExp("^\/\/ /[*/]+ +(START|END|[0-9][^:]+):").test(line)) || (new RegExp("^//[ *\t]*PREF:").test(line)) ) { // line starting /*... or //... followed by START: END: 1234: 1234a: linetype = "title"; } else if (regexes["user_pref"].rx.test(line)) { linetype = "user_pref"; secMore = false; subMore = false; titleMore = false; } else if (new RegExp("^// \\/[\\*\\/][ \t\\*\\/]*$").test(line)) { // line just /* // continue; } else if (new RegExp("^// [ \\t]*\\*+\\/[ \\t]*$").test(line)) { // line just */ secMore = false; subMore = false; titleMore = false; continue; } else if (titleMore == true) { linetype = "title+" } else if (secMore == true) { linetype = "section+" } else if (subMore == true) { if (new RegExp("^// [ \\t]*\/\/[ \\t]*FF[0-9]+\\+*[ \\t]*$").test(line)) { // new 'sub' for: // FF99 (so tags from sub carried over) linetype = "ff" } else { linetype = "sub+" } } else if (/^[ \t]*$/.test(line)) { // ignore empty line (as we are not within a description) continue; } else if (new RegExp("^// [ \\t]*\/\/[ \\t]*FF[0-9]+\\+*[ \\t]*$").test(line)) { // new 'sub' for: // FF99 (so tags from sub carried over) linetype = "ff" } else { linetype = "sub" } //////////////////////////////////////// // save unused prefTitle prefDesc (userjsTableView) //////////////////////////////////////// // (eg arkenfox/user.js 0105 0902 sub-sections have no user_pref) switch (linetype) { case "section": case "sub": case "ff": case "title": if (prefInfoSaved == false) { prefArray.push( { 'type': "prefx", 'title': prefTitle, 'desc': prefDesc, 'id': "" + prefid_prefix + prefid, 'name': "", 'state': "", 'value': "", 'comment': "", 'info': "", 'tagclass': "" } ); last_index = (prefArray.length - 1); prefInfoSaved = true; // add to info for (var i in tags) { tags[i].rx.lastIndex = 0; if ( // (tags[i].secflag) || // ignore tags in section headings (tags[i].subflag) || (tags[i].titleflag) || (tags[i].rx.test(prefArray[last_index].comment)) ) { if (tags[i].indicator) { prefArray[last_index].info += tags[i].indicator + ' '; } prefArray[last_index].tagclass += "TAGS_" + i.replace(/[^A-Za-z0-9_]/g, "__") + " "; tags[i].count++; } } } } //////////////////////////////////////// // reset some flags/variables (userjsTableView) //////////////////////////////////////// // (some are also reset in the line type detect above) switch (linetype) { case "section": case "sub": case "ff": case "title": prefTitle = ""; prefDesc = ""; } switch (linetype) { case "section": for (var i in tags) { tags[i].secflag = false; } for (var i in tags) { tags[i].subflag = false; } for (var i in tags) { tags[i].titleflag = false; } break; case "sub": for (var i in tags) { tags[i].subflag = false; } break; case "title": for (var i in tags) { tags[i].titleflag = false; } prefInfoSaved = false; break; } //////////////////////////////////////// // detect tags (userjsTableView) //////////////////////////////////////// switch (linetype) { case "section": case "section+": for (var i in tags) { tags[i].rx.lastIndex = 0; if (tags[i].rx.test(line)) { tags[i].secflag = true; }; } break; case "sub": case "sub+": for (var i in tags) { tags[i].rx.lastIndex = 0; if (tags[i].rx.test(line)) { tags[i].subflag = true; }; } break; case "title": case "title+": for (var i in tags) { tags[i].rx.lastIndex = 0; if (tags[i].rx.test(line)) { tags[i].titleflag = true; }; } } //////////////////////////////////////// // escape the text (userjsTableView) //////////////////////////////////////// // replace & < > characters with HTML escape codes line = escapeHtml(line); //////////////////////////////////////// // capture pref id (arkenfox/user.js ref) (userjsTableView) //////////////////////////////////////// if (linetype == "section" || linetype == "title") { if ( (new RegExp("^(?:\/\/ )/[*/]+[ \\t]+\\[SECTION ([^\\]]+)\\]:.*$").test(line)) || (new RegExp("^(?:\/\/ )/[*/]+[ \\t]+([^:]+):.*$").test(line)) ) { prefid = line; x = new RegExp("^(?:\/\/ )/[*/]+[ \\t]+\\[SECTION ([^\\]]+)\\]:.*$"); prefid = prefid.replace(x, "$1"); x = new RegExp("^(?:\/\/ )/[*/]+[ \\t]+([^:]+):.*$"); prefid = prefid.replace(x, "$1"); if (/^9999/.test(prefid)) { prefid_prefix = '9999:'; } else if (/^END/.test(prefid)) { prefid_prefix = ''; } } } //////////////////////////////////////// // style/linkify (userjsTableView) //////////////////////////////////////// // (pref done later as we only do //comment part) if (linetype != "user_pref") { regexes["http"].rx.lastIndex = 0; line = line.replace(regexes["http"].rx, regexes["http"].re); for (var i in tags) { if (tags[i].re) { tags[i].rx.lastIndex = 0; line = line.replace(tags[i].rx, tags[i].re); } } } //////////////////////////////////////// // section/section+/sub/sub+ (userjsTableView) //////////////////////////////////////// // heading (first line) if (linetype == "section" || linetype == "sub" || linetype == "ff") { prefArray.push( { 'type': linetype.replace(/^ff$/, "sub"), 'title': line.replace(/^\/\/ /, "") .replace(/^\/\*+[ \t]*/, "") .replace(/[ \t]*\*+\/[ \t]*$/, "") .replace(/^(\/\/ | \/\/ | \/\/ | \* |\* )/, ""), 'desc': "", 'id': "", 'name': "", 'state': "", 'value': "", 'comment': "", 'info': "", 'tagclass': "HEADING " } ); last_index = (prefArray.length - 1); // if line has a */ ending there will be no desc if (linetype == "section") { if (new RegExp("\\*\\*\\*\\/[ \\t]*$").test(line)) { secMore = false; } else { secMore = true; } } if (linetype == "ff") { subMore = false; } else { if (new RegExp("\\*\\*\\*\\/[ \\t]*$").test(line)) { subMore = false; } else { subMore = true; } } } // section+/sub+ (append to last saved entry in array) if (linetype == "section+" || linetype == "sub+") { prefArray[last_index].desc += line.replace(/^\/\/ /, "") .replace(/^\/\*+[ \t]*/, "") .replace(/[ \t]*\*+\/[ \t]*$/, "") // .replace(/^(\/\/ | \/\/ | \/\/ | \* |\* )/, "") + "<br>"; } //////////////////////////////////////// // title / title+ (userjsTableView) //////////////////////////////////////// // title/title+ can apply to multiple prefs // (only stored in array when we get to user_pref on subsequent lines) // title (first line) if (linetype == "title") { prefTitle = line; x = new RegExp("^(?:\/\/ )/[*/]+ +[^:]+:[ \t]*(.*)[ \t]*$"); prefTitle = prefTitle.replace(x, "$1") .replace(/^(\/\/ | \/\/ | \/\/ | \* |\* )/, ""); x = new RegExp("[ \\t]*\\*+/[ \\t]*$"); prefTitle = prefTitle.replace(x, ""); x = new RegExp("[ \\t]*\\*+/[ \\t]*$"); prefTitle = prefTitle.replace(x, ""); prefTitle += "<br>" if (new RegExp("\\*\\*\\*\\/[ \\t]*$").test(line)) { titleMore = false; } else { titleMore = true; } } // title+ if (linetype == "title+") { prefDesc += line.replace(/^\/\/ /, ""); //.replace(/^(\/\/ | \/\/ | \/\/ | \* |\* )/, ""); x = new RegExp("[ \\t]*\\*+/[ \\t]*$"); prefDesc = prefDesc.replace(x, ""); prefDesc += "<br>" if (new RegExp("\\*\\*\\*\\/[ \\t]*$").test(line)) { titleMore = false; } else { titleMore = true; } } //////////////////////////////////////// // pref (userjsTableView) //////////////////////////////////////// // (also store title/title+ collected from previous lines) if (linetype == "user_pref") { prefState = ""; prefName = ""; prefValue = ""; prefComment = ""; regexes["user_pref"].rx.lastIndex = 0; prefState = line.replace(regexes["user_pref"].rx, "$1"); regexes["user_pref"].rx.lastIndex = 0; prefName = line.replace(regexes["user_pref"].rx, "$3"); regexes["user_pref"].rx.lastIndex = 0; prefValue = line.replace(regexes["user_pref"].rx,"$5"); // do not remove preceeding '//' (for output clarity later) regexes["user_pref"].rx.lastIndex = 0; prefComment = line .replace(regexes["user_pref"].rx, "$7") .replace(/^[ \t]*/, ""); // .replace(/^(\/\/ | \/\/ | \/\/ | \* |\* )/, ""); // determine if inactive if (new RegExp("^.*[/][/*].*user_pref", "i").test(prefState)) { prefState = "Inactive"; } else { prefState = ""; } // only display first instance of parrot pref if (prefName == "_user.js.parrot") { if (ISawAParrot > -1) { // disabled: make first parrot display latest parrot value // prefArray[ISawAParrot].value = prefValue; continue; } // note index of first parrot (not yet appended) ISawAParrot = (prefArray.length); } // save to array prefArray.push( { 'type': "user_pref", 'title': prefTitle, 'desc': prefDesc, 'id': "" + prefid_prefix + prefid, 'name': prefName, 'state': prefState, 'value': prefValue, 'comment': prefComment, 'info': "", 'tagclass': ((prefState) ? "TAGS_Inactive " : "TAGS_Active ") } ); last_index = (prefArray.length - 1); // flag so we know prefTitle/prefDesc used // (those vars are not cleared as may be needed for next user_pref) prefInfoSaved = true; // add to info for (var i in tags) { tags[i].rx.lastIndex = 0; if ( // (tags[i].secflag) || // ignore tags in section headings (tags[i].subflag) || (tags[i].titleflag) || (tags[i].rx.test(prefArray[last_index].comment)) ) { if (tags[i].indicator) { prefArray[last_index].info += tags[i].indicator + ' '; } prefArray[last_index].tagclass += "TAGS_" + i.replace(/[^A-Za-z0-9_]/g, "__") + " "; tags[i].count++; } } // style/linkify comment regexes["http"].rx.lastIndex = 0; prefArray[last_index].comment = prefArray[last_index].comment .replace(regexes["http"].rx, regexes["http"].re); for (var i in tags) { tags[i].rx.lastIndex = 0; if (tags[i].re) { prefArray[last_index].comment = prefArray[last_index].comment .replace(tags[i].rx, tags[i].re); } } } // "user_pref" } input_content = []; lastSection++; //////////////////////////////////////// // end of parsing (userjsTableView) //////////////////////////////////////// //////////////////////////////////////// // calculate: stats (userjsTableView) //////////////////////////////////////// var stats = { 'total': { 'id': -2, 'sub': false, 'count': 0, 'name': "Total" }, 'totala': { 'id': -2, 'sub': true, 'count': 0, 'name': "Active" }, 'totali': { 'id': -2, 'sub': true, 'count': 0, 'name': "Inactive" }, } var statBase = "", statSuffix = ""; for (var i = 0, len = prefArray.length; i < len; i++) { if (prefArray[i].type == "user_pref") { if (prefArray[i].state == '') { stats["total"].count++; stats["totala"].count++; } else { stats["total"].count++; stats["totali"].count++; } } } //////////////////////////////////////// // start forming HTML (userjsTableView) //////////////////////////////////////// var content_html = '<div id="tableview_buttons_bar">' + '<span class="body_' + theme + '">'; // title eg arkenfox user.js (date) (version) if (/^[ \t\\\*]*name[ \t:]*(.*)[ \t]*$/m.test(input_box.value)) { content_html += '<h3 style="padding: 0px; margin: 0px; display:inline;">' + (/^[ \t\\\*]*name[ \t:]*(.*)[ \t]*$/m.exec(input_box.value)[1]) + '</h3> ' } if (/^[ \t\\\*]*date[ \t:]*(.*)[ \t]*$/m.test(input_box.value)) { content_html += "(" + (/^[ \t\\\*]*date[ \t:]*(.*)[ \t]*$/m.exec(input_box.value)[1]) + ") "; } if (/^[ \t\\\*]*(version.*)[ \t]*$/m.test(input_box.value)) { content_html += "(" + (/^[ \t\\\*]*(version.*)[ \t]*$/m.exec(input_box.value)[1]) + ")"; } content_html += '</span><br>' // collapse all button + '<button type="button" style="width:2em;" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" onclick="userjsTableViewCollapseAll();"' + ' title="Collapse All Descriptions"><b>-</b></button>' // expand all button + '<button type="button" style="width:2em;" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" onclick="userjsTableViewExpandAll();"' + ' title="Expand All Descriptions"><b>+</b></button>' // filter button select + '<select style="width:8em;" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" id="filter_select" onchange="userjsTableViewTagFilter();" ' + 'title="Filter tags (multiple choice)">' // multiple + ' <option value="" disabled selected hidden>▾ ▢ Filter</option>' + ' <option value="SHOWALL">Show all (clear filter)' + '    (' + stats["total"].count + ')</option>' + ' <option value="HIDEHEADINGS">▢  Hide headings</option>' + ' <option value="HIDEINACTIVE">▢  Hide inactive</option>' + ' <option value="HIDEACTIVE">▢  Hide active</option>' + ' <option value="INVERT">▢  Invert (show the opposite)</option>' // + ' <option disabled></option>' + ' <option value="TAGS_Active">▢  ' + 'Active' + '    (' + stats["totala"].count + ')</option>' + ' <option value="TAGS_Inactive">▢  ' + 'Inactive' + '    (' + stats["totali"].count + ')</option>'; // add tags to filter select (and show indicator/key plus counts) for (var i in tags) { if (tags[i].count > 0) { content_html += ' <option value="' + "TAGS_" + i.replace(/[^A-Za-z0-9_]/g, "__") + '">▢  '; if (tags[i].indicator) { content_html += tags[i].indicator + '  -  '; } content_html += i + '    (' + tags[i].count + ')</option>'; } } content_html += '</select>' // ref: http://www.amp-what.com/unicode/search/triangle // navigation: jumpback_button + '<button type="button" id="jumpback_button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" style="width:2em;" title="Jump back a section">▲</button>' // navigation: jumpnext_button + '<button type="button" id="jumpnext_button" class="controls borders' + ' controls_' + theme + ' borders_' + theme + '" style="width:2em;margin-right:0.5em;" ' + 'title="Jump to next section">▼</button>' // navigation: viewer_slider + '<input type="range" min="0" max="50" value="0" id="viewer_slider">' + '</div>' + '<div style="width: 100%;" id="tableview_div">' + '<br><br><br><br>' + '(see [Filter] button for Info key, and other * tags)<br>\n'; var index_select_html = '' + '<option value="" disabled selected hidden>▾Index</option>\n' + '<option value="0">(TOP)</option>\n'; var groups_container_html = '<a target="_blank" href="" id="group_link_showing_table_filter">-' + 'Current Table View (changes with [Filter] button use)' + '</a><br>\n'; //////////////////////////////////////// // build results table (userjsTableView) //////////////////////////////////////// // loop through array and build each table row // (either: section heading or user_pref rows) content_html += '<br><table id="table_tview">' content_html += '<tr>' + '<td class="td_tview_all td_tview_id">ID</td>' + '<td class="td_tview_all td_tview_name">Preference Name</td>' + '<td class="td_tview_all td_tview_value">Value</td>' + '<td class="td_tview_all td_tview_desc">Description</td>' + '<td class="td_tview_all td_tview_info">Info</td>' + '</tr>'; var section_heading = ""; var sectionCount = 0; var group_user_pref_list = ''; var group_user_pref_list_filter = ''; for (var i = 0, len = prefArray.length; i < len; i++) { if (prefArray[i].type == "section") { // section heading row (but not for sub-heading) sectionCount++; content_html += '<tr class="tr_tview_heading ' + prefArray[i].tagclass + '">' + '<td class="td_tview_all" colspan="5">' // anchor + '<span class="anchor" id="' + sectionCount + '"></span>' // section heading + ((prefArray[i].desc=="") ? "" : '<details class="det secDet"><summary>') + '<span class="pref">' + prefArray[i].title + '</span>' + ((prefArray[i].desc=="") ? "" : "</summary><br>") + prefArray[i].desc + ((prefArray[i].desc=="") ? "" : "</details>") + '</td></tr>'; // end and start about:config bookmarks groups if (sectionCount > 1) { group_user_pref_list = group_user_pref_list.replace(/\|$/, ''); groups_container_html += group_user_pref_list + ')(;|$)|^$/i">-' + section_heading + '</a><br>\n'; group_user_pref_list = ''; } groups_container_html += '<a target="_blank" href="about:config?filter=/^\\*$|^('; // note current section_heading for next time section_heading = prefArray[i].title; // add to index_select button index_select_html += '<option value=' + sectionCount + '>' + section_heading + '</option>\n'; } else { // user_pref and sub-heading row // (note: sub-headings are flagged in their prefArray[i].tagclass) content_html += '<tr class="tr_tview_pref ' // class for inactive user_pref + ((prefArray[i].state=="") ? "" : "tr_tview_inactive ") + prefArray[i].tagclass + '">' // pref id (arkenfox/user.js ref) + '<td class="td_tview_all td_tview_id">' + '<span class="ref">' + prefArray[i].id.replace(":", "<br>") + '</span></td>' // pref name + '<td class="td_tview_all td_tview_name">' + "<a target=\"_blank\" href=\"about:config?filter=/^\\*\$|^(" + prefArray[i].name.replace(/([*.+])/g, "\\$1") + ")(;|\$)|^\$/i\">" + prefArray[i].name + "</a>" + '</td>' // pref value (add class for color) + '<td class="td_tview_all td_tview_value">' + '<span class="' + prefArray[i].value .replace(/^[0-9.+-]+$/, "integer") .replace(/^(".*"|'.*')$/, "string") + '">' + prefArray[i].value + '</span></td>' // pref desc (title+desc+comment) (details tag) + '<td class="td_tview_all td_tview_desc">' // only use details tag if we have desc or comment + ( ((prefArray[i].desc=="" && prefArray[i].comment=="") || (prefArray[i].title=="" && prefArray[i].desc=="") ) ? "" : '<details class="det prefDet"><summary>') // title + ((prefArray[i].type == "sub") ? '<span class="pref" style="font-weight: normal">' : '<span class="ref" style="font-weight: normal">') + prefArray[i].title + '</span>' // close the summary (if used) + ( ((prefArray[i].desc=="" && prefArray[i].comment=="") || (prefArray[i].title=="" && prefArray[i].desc=="") ) ? "" : "</summary>") // desc + comment + prefArray[i].desc + prefArray[i].comment // close the details (if used) + ( ((prefArray[i].desc=="" && prefArray[i].comment=="") || (prefArray[i].title=="" && prefArray[i].desc=="") ) ? "" : "</details>") + '</td>' // pref info (state etc) + '<td class="td_tview_all td_tview_info">' + ((prefArray[i].state=="") ? "" : '<span class="pref">' + prefArray[i].state + '</span><br>') + prefArray[i].info .replace(new RegExp('<span class="setup">S</span> ' + '(<span class="setup">S.</span>)'), "$1") + '</td></tr>'; // collect prefname for pref groups if (prefArray[i].name) { group_user_pref_list += prefArray[i].name.replace(/([*.+])/g, "\\$1") + "|"; group_user_pref_list_filter += prefArray[i].name.replace(/([*.+])/g, "\\$1") + "|"; } } } content_html += '</table>'; // bottom anchor sectionCount++; content_html += '<span class="anchor" id="' + sectionCount + '"></span>' index_select_html += '<option value="' + sectionCount + '">(BOTTOM)</option>\n'; // end about:config bookmarks groups group_user_pref_list = group_user_pref_list.replace(/\|$/, ''); groups_container_html += group_user_pref_list + ')(;|$)|^$/i">-' + section_heading + '</A><br>\n'; //////////////////////////////////////// // end content and add into document (userjsTableView) //////////////////////////////////////// content_html += '</div><br><br><br><br>\n'; document.getElementById("view_area").innerHTML = content_html; document.getElementById("index_select").innerHTML = index_select_html; // also remove first group if unused document.getElementById("groups_container").innerHTML = groups_container_html.replace(new RegExp('<a target="_blank"' + ' href="about:config\\?filter=' + '\\/\\^\\\\\\*\\$\\|\\^\\(\\)\\(;\\|\\$\\)\\|\\^\\$\\/i">' + '-Introduction<\\/A><br>'), ""); document.getElementById("group_link_showing_table_filter").href = 'about:config?filter=/^\\*$|^(' + group_user_pref_list_filter.replace(/\|$/, '') + ')(;|$)|^$/i'; content_html = null; index_select_html = null; groups_container_html = null; // back document.getElementById("jumpback_button").addEventListener("click", function() { var e = document.getElementsByClassName("anchor"); var id = 0; for (var i = 0, j = e.length; i<j; i++) { if (e[i].getBoundingClientRect().y >= 0) { break; } else { id = i + 1; } } document.getElementById("viewer_slider").value = id; if (id == 0) { scroll(0,0); } else { document.getElementById(id).scrollIntoView(); } }); // next document.getElementById("jumpnext_button").addEventListener("click", function() { var e = document.getElementsByClassName("anchor"); var id = 1; for (var i = 0, j = e.length; i<j; i++) { if (e[i].getBoundingClientRect().y > 1) { break; } else { id = i + 2; } } document.getElementById("viewer_slider").value = id; if (id == 0) { scroll(0,0); } else { document.getElementById(id).scrollIntoView(); } }); // nav slider usage jump to position document.getElementById("viewer_slider").max = sectionCount; for (const event of [ "input", "click" ]) { document.getElementById("viewer_slider").addEventListener(event, function() { if (this.value == 0) { scroll(0,0); } else { document.getElementById(this.value).scrollIntoView(); } }); } // refresh as per UI settings togglePrefixAboutConfigLinks("refresh"); if (toggleExpandAllOnView("status")) { userjsTableViewExpandAll(true); } if (toggleGroupsOnView("status")) { toggleGroupsPanel(true); } } /* end function userjsTableView */ // ************************************* // convertListToGroup // ************************************* function convertListToGroup(box_name) { var box = document.getElementById(box_name); var x, r; // change \\* \\. \\+ to * . + box.value = box.value.replace(/\\\\([*.+])/gm, "$1"); box.value = box.value.replace(/\\([*.+])/gm, "$1"); // change non user pref characters (<- include :/) to newline delimiters box.value = box.value.replace(/[^a-zA-Z0-9._*-:/]+/gm, "\n"); // blank lines beginning http: https: box.value = box.value.replace(/^https?:.*$/gm, ""); // change non user pref characters to newline delimiters box.value = box.value.replace(/[^a-zA-Z0-9._*-]+/gm, "\n"); // remove end of line period box.value = box.value.replace(/\.$/gm, ""); // blank lines not containing a period box.value = box.value.replace(new RegExp("^[^.\n]+$", "gm"), ""); // blank lines not beginning a-z box.value = box.value.replace(/^[^a-zA-Z\n].*$/gm, ""); // blank lines such as: e.g i.e box.value = box.value.replace(/^[a-z]\.[a-z]$/gm, ""); // remove excess newlines box.value = box.value.replace(/[\r\n]+/g, "\n"); box.value = box.value.replace(/^[\r\n]+/g, ""); box.value = box.value.replace(/[\r\n]+$/g, ""); // style as user_pref box.value = box.value.replace(/^.*$/gm, "user_pref(\"$&\", null);"); updateDateTimeStampVariable(); box.value = '/*** ' + date_time_stamp + ' preference group (from list) ***/\n\n' + box.value toggleTextAreaReadOnly(box_name, true); } /* end function convertListToGroup */ // ************************************* // prefsjsClean // ************************************* function prefsjsClean(input_box_name, output_box_name) { var input_box = document.getElementById(input_box_name); var output_box = document.getElementById(output_box_name); var input_content = input_box.value.replace(/(\r\n|\r)/g,'\n').split("\n"); var i = 0; for (i in input_content) { var line = input_content[i]; var x; var prefName; x = new RegExp("^(.*user_pref)" // $1 user_pref //user_pref etc + "([ \t]*\\([ \t]*[\"'])" // $2 (" (' + "([^\"']+)" // $3 prefname + "([\"'][ \t]*,[ \t]*)" // $4 ", ', + "(.*)" // $5 prefvalue "prefvalue" 'prefvalue' + "([ \t]*\\)[ \t]*;)" // $6 ); + "(.*)$", "gi"); // $7 afters/comment if (x.test(line)) { prefName = line.replace(x,"$3"); x = new RegExp("^.*user_pref[ \t]*\\([ \t]*[\"']" + RegExp.escape(prefName) + "[\"'].*$", "gm"); output_box.value = output_box.value.replace(x, ""); } // remove excess newlines (without messing up the header section) output_box.value = output_box.value.replace(/[\r\n][\r\n][\r\n]+/g, "\n"); output_box.value = output_box.value.replace(/\.[\r\n]+user_pref\(/g, ".\n\nuser_pref\("); output_box.value = output_box.value.replace(/;[\r\n]+([ \t\/]*user_pref\()/g, ";\n$1"); output_box.value = output_box.value.replace(/[\r\n]+$/g, "\n"); } input_content = []; } /* end function prefsjsClean */ /* ************************************* */ /* various control functions (for buttons/selects/initial) */ /* ************************************* */ // ************************************* // findMenuSelectOption // ************************************* function findMenuSelectOption(name_match) { var e = document.getElementById("menu_select"); var found; for (var i=0,j=e.options.length;i<j;i++) { if (new RegExp(name_match, "i").test(e.options[i].textContent)) { if (typeof found == "number") { // more than one match found = null; break; } found = i; } } return found; } // ************************************* // statusMenuSelectOption // ************************************* function statusMenuSelectOption(i) { // a selected/on option will be prefixed with // WHITE SQUARE CONTAINING BLACK SMALL SQUARE if (typeof i == "number") { if ( /^[\u{25A3}]/u.test(document.getElementById("menu_select").options[i].textContent) ) { return true; } else { return false; } } else { return; } } // ************************************* // updateMenuSelectOption // ************************************* function updateMenuSelectOption(i, on) { if (typeof i == "string") { i = findMenuSelectOption(i); } if (typeof i == "number") { var t = document.getElementById("menu_select").options[i]; t.textContent = t.textContent.replace(/^[\u{25A2}\u{25A3}\u{25EB}]+/u, ""); if (typeof on !== "boolean") { // prefix when option is not a toggle // WHITE SQUARE WITH VERTICAL BISECTING LINE t.textContent = "\u25EB" + t.textContent; } else if (on) { // prefix when option selected (on/true) // WHITE SQUARE CONTAINING BLACK SMALL SQUARE t.textContent = "\u25A3" + t.textContent; } else { // prefix when option not selected (off/false) // WHITE SQUARE WITH ROUNDED CORNERS t.textContent = "\u25A2" + t.textContent; } } } // ************************************* // toggleMenuSelectOption // ************************************* function toggleMenuSelectOption(name, new_state) { // new_state: "toggle" (default) "on" "off" "refresh" "status" var opt = findMenuSelectOption(name); var on = statusMenuSelectOption(opt); if (new_state !== "status") { if (new_state === "on") { on = true; } else if (new_state === "off") { on = false; } else if (new_state !== "refresh") { on = !on; } updateMenuSelectOption(opt, on); } return on; } function toggleExpandAllOnView(new_state) { return toggleMenuSelectOption("expand all on view", new_state); } function toggleGroupsOnView(new_state) { return toggleMenuSelectOption("show groups on view", new_state); } function toggleViewPlusOnView(new_state) { return toggleMenuSelectOption("view\\+ style", new_state); } function toggleSetWrapOnMenu(new_state) { return toggleMenuSelectOption("wrap", new_state); } // ************************************* // refocusSection // ************************************* /* section focus */ function refocusSection() { if (!section_focus) { section_focus = document.getElementById("TOP-Introduction"); } section_focus.focus(); // if (section_focus == document.getElementById("TOP-Introduction")) { // section_focus.scrollIntoView(false); // } // else { section_focus.scrollIntoView(); // body_height - (scroll_position + window_size) var position_from_bottom = Math.max(document.body.offsetHeight - (window.pageYOffset + window.innerHeight), 0); if ( document.getElementById("collect_button").style.textDecoration == "line-through" ) { if (position_from_bottom > 200) { window.scrollBy(0, -200); } } else { if (position_from_bottom > 70) { window.scrollBy(0, -70); } } // } } // ************************************* // togglePrefixAboutConfigLinks // ************************************* /* prefix about:config links (about:blank#about:config) */ function togglePrefixAboutConfigLinks(new_state) { // new_state: "toggle" (default) "on" "off" "refresh" // "aboutblank#aboutconfig-toggle" "aboutblank#function-toggle" var aboutblank_opt = findMenuSelectOption("prefix about:config"); var function_opt = findMenuSelectOption("function about:config"); var aboutblank_on = false, function_on = false; if (new_state === "on") { aboutblank_on = true; function_on = false; } else if (new_state === "off") { aboutblank_on = false; function_on = false; } else if (new_state === "aboutblank#function-toggle") { aboutblank_on = false; function_on = statusMenuSelectOption(function_opt); // toggle function_on = !function_on; } else { // get current status aboutblank_on = statusMenuSelectOption(aboutblank_opt); function_on = statusMenuSelectOption(function_opt); // toggle if not refresh if (new_state !== "refresh") { // "aboutblank#aboutconfig-toggle" aboutblank_on = !aboutblank_on; function_on = false; } } // update var o = document.getElementsByTagName("a"); for (var i=0,j=o.length;i<j;i++) { // remove unwanted style if (!function_on) { // remove the function prefixes if (/^about:blank#ujtFindPref/.test(o[i].href)) { o[i].href = o[i].href.replace( /^about:blank#ujtFindPref\({style:3,name:(.*)}\);$/, 'about:config?filter=$1'); } } if (!aboutblank_on) { // remove the about:blank# prefixes if (/^about:blank#about:config./.test(o[i].href)) { o[i].href = o[i].href.replace(/^about:blank#/,''); } } // add required style if (aboutblank_on) { // add the about:blank# prefixes if (/^about:config./.test(o[i].href)) { o[i].href = "about:blank#" + o[i].href; } } else if (function_on) { // add the function prefixes if (/^about:config\?filter=/.test(o[i].href)) { o[i].href = o[i].href.replace( /^about:config\?filter=(.*)$/, 'about:blank#ujtFindPref({style:3,name:$1});' ); } } } updateMenuSelectOption(aboutblank_opt, aboutblank_on); updateMenuSelectOption(function_opt, function_on); } /* end function togglePrefixAboutConfigLinks */ // ************************************* // applyFolderForLocalLinks // ************************************* // local links that appear over the text boxes // change them from .html files location to specified location function applyFolderForLocalLinks(folder) { var e = document.getElementsByClassName("local_folder"); for (var i=0,j=e.length;i<j;i++) { e[i].href = e[i].href.replace(/^.*\//, folder.replace(/\/$/, "") + '/'); } } // ************************************* // togglePanel // ************************************* // force: false for hide true for show unset for toggle function togglePanel(div_name, force) { var optInSelect = "", optButton = ""; if (div_name == "back_button") { // get the current div_name var e = document.getElementsByClassName("panels"); for (var i = 0, j = e.length; i<j; i++) { if (e[i].style.display == "block") { div_name = e[i].id; break; } } } if (div_name == "actions_panel" || div_name == "back_button") { optInSelect = findMenuSelectOption("\\[Actions\\]"); div_name = "actions_panel" } else if (div_name == "groups_panel") { optInSelect = findMenuSelectOption("\\[Groups\\]"); optButton = "groups_button"; } else if (div_name == "links_panel") { optInSelect = findMenuSelectOption("\\[Links\\]"); } else if (div_name == "functions_panel") { optInSelect = findMenuSelectOption("\\[about:config Functions\\]"); } else if (div_name == "help_panel") { optInSelect = findMenuSelectOption("\\[help/info\\]"); } else { optButton = div_name + "_button"; if (div_name == "all-vertical") { optButton = "all_button" } } // function to toggle different elements var show = function(status, type, ids) { for (const id of ids) { if (type == "display") { document.getElementById(id).style.display = status ? "block" : "none"; } else if (type == "display-inline") { document.getElementById(id).style.display = status ? "inline-block" : "none"; } else if (type == "button") { document.getElementById(id).style.fontWeight = status ? 'bold' : null; document.getElementById(id).style.borderWidth = status ? '4px' : null; } else if (type == "outer") { document.getElementById(id).style.borderBottom = status ? "1px solid" : null; } else if (type == "inner") { document.getElementById(id).style.display = status ? "block" : "none"; document.getElementById(id).style.width = status ? "50%" : null; document.getElementById(id).style.float = status ? "left" : null; } else if (type == "menu") { updateMenuSelectOption(id, status); } } } if ( (force === false) || ((force !== true) && (document.getElementById(div_name).style.display === "block")) ) { // hide a panel (not for divs - they are forced true) if (document.getElementById(div_name).style.display === "block") { show(false, "display", [ div_name ] ); show(false, "menu", [ optInSelect ] ); document.getElementById("back_button").title = "Back to [Actions] (input panel)" if (optButton) { show(false, "button", [ optButton ] ) } } // if collector mode is on, show actions_panel (which then shows div_2_overrides) if (document.getElementById("collect_button").style.textDecoration == "line-through") { togglePanel("actions_panel"); } else if (return_to_panel) { togglePanel(return_to_panel); return_to_panel = ""; } } else { // show a panel/boxdiv (divs always forced true) if (div_name == "div_1_template" || div_name == "div_2_overrides" || div_name == "div_3_userjs" || div_name == "div_4_other" || div_name == "all" || div_name == "all-vertical" ) { // hide boxdivs and un-highlight buttons show(false, "inner", [ "div_1_template", "div_2_overrides", "div_3_userjs", "div_4_other" ] ); show(false, "outer", [ "div_1_2", "div_3_4" ] ); show(false, "button", [ "div_1_template_button", "div_2_overrides_button", "div_3_userjs_button", "div_4_other_button", "all_button" ] ); } else { // hide panels and un-highlight menu/buttons show(false, "display", [ "actions_panel", "groups_panel", "links_panel", "functions_panel", "help_panel" ] ); show(false, "menu", [ "\\[Actions\\]", "\\[Groups\\]", "\\[Links\\]", "\\[about:config Functions\\]", "\\[help/info\\]" ] ); show(false, "button", [ "groups_button" ] ); // highlight menu and show close button if (optInSelect) { show(true, "menu", [ optInSelect ] ) } document.getElementById("back_button").title = "Close [" + div_name.replace(/^(.).*$/, "$1").toUpperCase() + div_name.replace(/_/, "] ").replace(/^./, ""); } // highlight button if (optButton) { show(true, "button", [ optButton ] ) } // show required panel/boxdivs if (div_name == "all") { show(true, "inner", [ "div_1_template", "div_2_overrides", "div_3_userjs", "div_4_other" ] ); show(true, "outer", [ "div_1_2", "div_3_4" ] ); } else if (div_name == "all-vertical") { show(true, "display", [ "div_1_template", "div_2_overrides", "div_3_userjs", "div_4_other" ] ); } else { show(true, "display", [ div_name ] ); document.getElementById(div_name).focus(); } // collect mode + actions/overrides = focus overrides if ( document.getElementById("collect_button").style.textDecoration == "line-through" && ( div_name == "actions_panel" || div_name == "div_2_overrides" ) ) { // display overrides boxdiv if (div_name == "actions_panel") { // hide show(false, "inner", [ "div_1_template", "div_2_overrides", "div_3_userjs", "div_4_other" ] ); show(false, "outer", [ "div_1_2", "div_3_4" ] ); show(false, "button", [ "div_1_template_button", "div_3_userjs_button", "div_4_other_button", "all_button" ] ); // show show(true, "display", [ "div_2_overrides" ] ); show(true, "button", [ "div_2_overrides_button" ] ); } // focus overrides input (as actions_panel is currently a smaller size) var e = document.getElementById("box_2_overrides"); e.scrollIntoView(); e.focus(); // position cursor to the end e.selectionEnd = e.value.length; e.selectionStart = e.value.length; } } } /* end function togglePanel */ function toggleActionsPanel(force) { togglePanel("actions_panel", force); } function toggleGroupsPanel(force) { togglePanel("groups_panel", force); } function toggleLinksPanel(force) { togglePanel("links_panel", force); } function toggleFunctionsPanel(force) { togglePanel("functions_panel", force); } function toggleHelpPanel(force) { togglePanel("help_panel", force); } function backButton(force) { togglePanel("back_button", force); } function toggleTemplateDiv() { togglePanel("div_1_template", true); } function toggleOverridesDiv() { togglePanel("div_2_overrides", true); } function toggleUserjsDiv() { togglePanel("div_3_userjs", true); } function toggleOtherDiv() { togglePanel("div_4_other", true); } function toggleAllDiv() { togglePanel("all", true); } function toggleAllDivVertical() { togglePanel("all-vertical", true); } function checkIfAllButtonSelected() { if (document.getElementById("all_button").style.borderWidth != '4px') { return false } return true } // ************************************* // setExpandAll (expand/collapse all sections) // ************************************* function setExpandAll(on = true) { var e = document.getElementsByClassName("content"); if (e.length > 0) { for (var i=0, j=e.length; i<j; i++) { e[i].style.display = on ? "block" : "none"; } } } // ************************************* // setWrap (wrap text) // ************************************* function setWrap(new_state) { // new_state: "toggle" (default) "on" "off" "refresh" var on = toggleSetWrapOnMenu(new_state); var e = document.getElementsByClassName("content"); for (var i=0, j=e.length; i<j; i++) { e[i].style.whiteSpace = on ? "pre-wrap" : "pre"; } } // ************************************* // applyFontSize // ************************************* /* font resize */ function applyFontSize(size) { //var e = document.getElementById("view_area"); var e = document.body; // size: percent increase/decrease amount eg 100% -10 +10 if (e.style.fontSize == "") { e.style.fontSize = "100%"; } size = size.replace(/[% ]/g, ""); if (/^-/.test(size)) { size = size.replace(/^-([0-9]+)$/, "$1"); size = (parseFloat(e.style.fontSize) - parseFloat(size)); } else if (/^\+/.test(size)) { size = size.replace(/^\+([0-9]+)$/, "$1"); size = (parseFloat(e.style.fontSize) + parseFloat(size)); } // half some top button size on larger font size // if (size > 200) { // for (const id of [ "groups_button" ]) { // document.getElementById(id).style.width = "2.5em"; // } // } // else if (parseFloat(e.style.fontSize) > 200) { // // re-show the extra button // for (const id of [ "groups_button" ]) { // document.getElementById(id).style.width = "5em"; // } // } e.style.fontSize = size + "%"; // update select element first option text to show the new percent document.getElementById("size_select").options[0].textContent = "\u25BE" + e.style.fontSize; } // ************************************* // addThemeClass // ************************************* /* change color theme */ function addThemeClass(type, name, remove, add) { // get elements var e; if (type == "body") { e = document.body; } else if (type == "id") { e = document.getElementById(name); } else if (type == "tag") { e = document.getElementsByTagName(name); } else if (type == "class") { e = document.getElementsByClassName(name); } // remove/apply class to elements if (type == "body"||type == "id") { if (remove) { changeClass(e, name + "_" + remove, ""); } if (add) { changeClass(e, "", name + "_" + add); } } else { for (var i=0,j=e.length;i<j;i++) { if (remove) { changeClass(e[i], name + "_" + remove, ""); } if (add) { changeClass(e[i], "", name + "_" + add); } } } } // ************************************* // applyTheme // ************************************* function applyTheme(newtheme) { // remove any prefix eg "Theme: dark" = "dark" newtheme = newtheme.toLowerCase().replace( /^[^:]+: /, ''); // get current theme from body class eg "body_light" = "light" var oldtheme = document.body.className.replace( /(^| *)[^_]+_/ , ''); // usage: addThemeClass(type, name, remove, add) // eg addThemeClass("id", "view_area", "light", "dark") // removes class view_area_light from id=view_area adds view_area_dark addThemeClass("body" , "body" , oldtheme, newtheme); addThemeClass("id" , "view_area", oldtheme, newtheme); addThemeClass("class", "view_area", oldtheme, newtheme); addThemeClass("class", "panels" , oldtheme, newtheme); addThemeClass("class", "controls" , oldtheme, newtheme); addThemeClass("class", "borders" , oldtheme, newtheme); invertColorForClass("overrides"); updateMenuSelectOption("theme: " + oldtheme, false); updateMenuSelectOption("theme: " + newtheme, true); } // ************************************* // selectTextAreaContents // ************************************* /* functions used for actions panel options */ function selectTextAreaContents(text_box_name) { var e = document.getElementById(text_box_name); if (e.selectionStart == 0 && e.selectionEnd == e.value.length) { // all text is selected already, so move cursor to end e.selectionEnd = e.value.length; e.selectionStart = e.value.length; } else { e.select(); } e.focus(); } // ************************************* // toggleTextAreaReadOnly // ************************************* function toggleTextAreaReadOnly(text_box_name, force, falseifempty = true) { var e = document.getElementById(text_box_name); var d = document.getElementById(text_box_name + "_ro") if ( (falseifempty && e.value.replace(/[\r\n\t ]/g,"") == "") || (force === false) || ((force !== true) && (e.readOnly == true)) ) { e.readOnly = false; d.innerHTML = d.innerHTML.replace(/\u{25A3}/u, "\u25A2"); } else { e.readOnly = true; d.innerHTML = d.innerHTML.replace(/\u{25A2}/u, "\u25A3"); } } // ************************************* // readOnlySelectTextAreaContents // ************************************* function readOnlySelectTextAreaContents(text_box_name) { var e = document.getElementById(text_box_name); toggleTextAreaReadOnly(text_box_name, true); e.select(); e.focus(); } // ************************************* // clearTextAreaContents // ************************************* function clearTextAreaContents(text_box_name) { var e = document.getElementById(text_box_name); e.value = ""; e.style.backgroundColor = null; /*reset to initial*/ toggleTextAreaReadOnly(text_box_name, false); // e.focus(); } // ************************************* // handleActionURLparameter // ************************************* function handleActionURLparameter() { var action = getURLVariable("action"); switch (action) { case "view0": toggleActionsPanel(true); break; case "view1": document.getElementById("view_template_button").click(); break; case "view2": document.getElementById("view_overrides_button").click(); break; case "view3": document.getElementById("view_userjs_button").click(); break; case "view4": document.getElementById("view_other_button").click(); break; case "table1": document.getElementById("tableview_template_button").click(); break; case "table2": document.getElementById("tableview_overrides_button").click(); break; case "table3": document.getElementById("tableview_userjs_button").click(); break; case "table4": document.getElementById("tableview_other_button").click(); break; case "groups1": document.getElementById("view_template_button").click(); document.getElementById("groups_button").click(); break; case "groups2": document.getElementById("view_overrides_button").click(); document.getElementById("groups_button").click(); break; case "groups3": document.getElementById("view_userjs_button").click(); document.getElementById("groups_button").click(); break; case "groups4": document.getElementById("view_other_button").click(); document.getElementById("groups_button").click(); break; case "compare:"+(action.replace(/^compare:/, "")): userjsCompareLauncher(action); break; case "links": case "append": case "skeleton": case "collect": case "reduce": case "clean": case "byvalue": case "togroup": case "functions": case "help": document.getElementById(action+"_button").click(); break; } } /* end of function handleActionURLparameter */ // ************************************* // userjstoolSetupInital // ************************************* function userjstoolSetupInital() { updateDateTimeStampVariable(); document.getElementById("hiddendate").innerHTML = document.getElementById("hiddendate").innerHTML .replace(/(--userjs_)[0-9_]*/, "$1" + date_time_stamp); document.body.style.whiteSpace = "nowrap"; // hide the "this needs JavaScript" message document.getElementById("help_panel_nojs_div").style.display = "none"; document.getElementById("index_select").selectedIndex=0; document.getElementById("size_select").selectedIndex=0; document.getElementById("menu_select").selectedIndex=0; // add user.js code into the text boxes if it was encoded in the body // use .innerHTML rather than (.innerText || .textContent) document.getElementById("box_1_template").value = b64DecodeUnicode(document.getElementById("base64_box_1").innerHTML); document.getElementById("box_2_overrides").value = b64DecodeUnicode(document.getElementById("base64_box_2").innerHTML); document.getElementById("box_3_userjs").value = b64DecodeUnicode(document.getElementById("base64_box_3").innerHTML); document.getElementById("box_4_other").value = b64DecodeUnicode(document.getElementById("base64_box_4").innerHTML); // add content for about:config functions var e = document.getElementById("functions_panel_textarea"); e.value = b64DecodeUnicode( document.getElementById("base64_aboutconfig_functions").innerHTML ); e.readOnly = true; // add choices (and URLs) to the load buttons for (const id of [ "loadsave_template_select", "loadsave_overrides_select", "loadsave_userjs_select", "loadsave_other_select", ] ) { document.getElementById(id).innerHTML += '<option value="LOCAL">Load local file [Browse]</option>'; document.getElementById(id).innerHTML += '<option value="BUTTON">▢  ' + 'Show [Browse] buttons (if above fails)</option>'; document.getElementById(id).innerHTML += '<option value="URL">Download file from URL (if site allows)</option>'; // if url parameters where given show these as options for (const load of [ "load1", "load2", "load3", "load4" ] ) { var loadurl = getURLVariable(load); if (loadurl != "" && loadurl != null) { document.getElementById(id).innerHTML += '<option value="' + loadurl + '" title="' + loadurl + '">' + loadurl + '</option>'; } } // pickup URLs from links_panel where name is tagged with '#' var o = document.getElementById("links_panel").getElementsByTagName("a"); for (var i=0,j=o.length;i<j;i++) { if (/# *$/.test(o[i].innerHTML)) { document.getElementById(id).innerHTML += '<option value="' + o[i].href + '" title="' + o[i].href + '">' + "Download " + o[i].innerHTML.replace(/ *# *$/, "") //+ (/github/.test(o[i].href) ? ' *' : '') + '</option>'; } } document.getElementById(id).innerHTML += '<option value="SAVE">Save box content as a file</option>'; } // ensure initial "add content" message is visible setExpandAll(true); } /* end function userjstoolSetupInital */ // ************************************* // userjstoolSetupURLvariables // ************************************* function userjstoolSetupURLvariables() { if (getURLVariable("theme")) { applyTheme(getURLVariable("theme")); } else { applyTheme("default"); } if (getURLVariable("size")) { applyFontSize(getURLVariable("size")); } // else { // // larger text size for mobile // if (/(android|mobile)/i.test(navigator.userAgent)) { // applyFontSize("150%"); // } // } if (getURLVariable("wrap") == "true") { setWrap("on"); } if (getURLVariable("expand") == "true") { toggleExpandAllOnView("on"); } if (getURLVariable("groups") == "true") { toggleGroupsOnView("on"); } if (getURLVariable("viewplus") != "false") { toggleViewPlusOnView("on"); } if (getURLVariable("prefix") == "true") { togglePrefixAboutConfigLinks("aboutblank#aboutconfig-toggle"); } else if (getURLVariable("prefix") == "function") { togglePrefixAboutConfigLinks("aboutblank#function-toggle"); } // folder for links to local files // (if used off-line "local links" default to .html location) if (getURLVariable("folder")) { applyFolderForLocalLinks(getURLVariable("folder")); } else if (window.location.protocol != "file:") { // if on-line version used, stop the "local links" working as we do // not want them pointing to non-existing files at the on-line location applyFolderForLocalLinks("file:"); } } /* end function userjstoolSetupURLvariables */ // ************************************* // userjstoolSetupEventListeners // ************************************* function userjstoolSetupEventListeners() { /* index_select (jump to section) */ document.getElementById("index_select").addEventListener("change", function() { // expand, focus, and scroll to the chosen section var id = document.getElementById("index_select").value; // make the select element show first choice (button title) this.selectedIndex = 0; this.blur(); if (id) { if ( /(compare_div|tableview_div)/ .test(document.getElementById("view_area").innerHTML) ) { /* for compare or tableview */ document.getElementById("viewer_slider").value = id; if (id == 0) { scroll(0,0); } else { document.getElementById(id).scrollIntoView(); } } else { var e = document.getElementById(id); if (e) { e.nextElementSibling.style.display = "block"; section_focus = e; refocusSection(); } else if (id == "(TOP) / Introduction") { scroll(0,0); } } } }); /* groups button */ document.getElementById("groups_button").addEventListener("click", function() { return_to_panel = ""; toggleGroupsPanel(); this.blur(); }); /* font size select */ document.getElementById("size_select").addEventListener("change", function() { // change the font size to the value chosen // also changes the select element first choice name which we // are using as a title and it shows the current font size percent applyFontSize(document.getElementById("size_select").value); // make the select element show first choice (title) document.getElementById("size_select").selectedIndex = 0; this.blur(); }); /* menu select (...) */ document.getElementById("menu_select").addEventListener("change", function() { if (/expand all on view/i.test(this.value)) { toggleExpandAllOnView(); } else if (/collapse all/i.test(this.value)) { if (/tableview_div/.test(document.getElementById("view_area").innerHTML)) { userjsTableViewCollapseAll(); } else { setExpandAll(false); if (section_focus) { refocusSection(); } } } else if (/expand all/i.test(this.value)) { if (/tableview_div/.test(document.getElementById("view_area").innerHTML)) { userjsTableViewExpandAll(); } else { setExpandAll(true); if (section_focus) { refocusSection(); } } } else if (/collapse current/i.test(this.value)) { if (/tableview_div/.test(document.getElementById("view_area").innerHTML)) { userjsTableViewCollapseSectionDesc(); } else if (section_focus) { if (section_focus.nextElementSibling != null) { section_focus.nextElementSibling.style.display = "none"; } refocusSection(); } } else if (/wrap/i.test(this.value)) { setWrap("toggle"); } else if (/view\+ style/i.test(this.value)) { toggleViewPlusOnView(); } else if (/prefix about:config/i.test(this.value)) { togglePrefixAboutConfigLinks("aboutblank#aboutconfig-toggle"); } else if (/function about:config/i.test(this.value)) { togglePrefixAboutConfigLinks("aboutblank#function-toggle"); } else if (/other themes/i.test(this.value)) { this.selectedIndex = 0; var new_value = prompt("Please input theme name.\n" + "default,dark,light,mono,inverse,none,...\n" + "(or on open: userjs-tool.html?theme=light)"); applyTheme(new_value); } else if (/theme/i.test(this.value)) { applyTheme(this.value); } else if (/\[Actions\]/i.test(this.value)) { return_to_panel = ""; toggleActionsPanel(); } else if (/show groups on view/i.test(this.value)) { toggleGroupsOnView(); } else if (/\[Groups\]/i.test(this.value)) { return_to_panel = ""; toggleGroupsPanel(); } else if (/\[Links\]/i.test(this.value)) { toggleLinksPanel(); } else if (/\[about:config Functions\]/i.test(this.value)) { toggleFunctionsPanel(); } else if (/\[Help\/Info\]/i.test(this.value)) { toggleHelpPanel(); } // make the select element show first choice (title) this.selectedIndex = 0; this.blur(); }); /* readonly toggles */ document.getElementById("box_1_template_ro").addEventListener("click", function() { toggleTextAreaReadOnly("box_1_template"); this.blur(); }); document.getElementById("box_2_overrides_ro").addEventListener("click", function() { toggleTextAreaReadOnly("box_2_overrides"); this.blur(); }); document.getElementById("box_3_userjs_ro").addEventListener("click", function() { toggleTextAreaReadOnly("box_3_userjs"); this.blur(); }); document.getElementById("box_4_other_ro").addEventListener("click", function() { toggleTextAreaReadOnly("box_4_other"); this.blur(); }); /* buttons for actions_panel options */ // view document.getElementById("view_template_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_1_template").value == "") ) { toggleTextAreaReadOnly("box_1_template", true); toggleActionsPanel(false); userjsViewer("box_1_template"); } }); document.getElementById("view_overrides_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_2_overrides").value == "") ) { toggleTextAreaReadOnly("box_2_overrides", true); toggleActionsPanel(false); userjsViewer("box_2_overrides"); } }); document.getElementById("view_userjs_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_3_userjs").value == "") ) { toggleTextAreaReadOnly("box_3_userjs", true); toggleActionsPanel(false); userjsViewer("box_3_userjs"); } }); document.getElementById("view_other_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_4_other").value == "") ) { toggleTextAreaReadOnly("box_4_other", true); toggleActionsPanel(false); userjsViewer("box_4_other"); } }); // tableview document.getElementById("tableview_template_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_1_template").value == "") ) { toggleTextAreaReadOnly("box_1_template", true); toggleActionsPanel(false); userjsTableView("box_1_template"); } }); document.getElementById("tableview_overrides_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_2_overrides").value == "") ) { toggleTextAreaReadOnly("box_2_overrides", true); toggleActionsPanel(false); userjsTableView("box_2_overrides"); } }); document.getElementById("tableview_userjs_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_3_userjs").value == "") ) { toggleTextAreaReadOnly("box_3_userjs", true); toggleActionsPanel(false); userjsTableView("box_3_userjs"); } }); document.getElementById("tableview_other_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_4_other").value == "") ) { toggleTextAreaReadOnly("box_4_other", true); toggleActionsPanel(false); userjsTableView("box_4_other"); } }); // select document.getElementById("select_template_button").addEventListener("click", function() { selectTextAreaContents("box_1_template"); }); document.getElementById("select_overrides_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { // when collector mode is off selectTextAreaContents("box_2_overrides"); } else { // when collector mode is on var e = document.getElementById("box_2_overrides"); if ( (e.selectionStart == 0 && e.selectionEnd == e.value.length) || (e.selectionStart == e.selectionEnd) ) { // when all or nothing is selected, select the opposite selectTextAreaContents("box_2_overrides"); } else { // when text is already selected we will just focus // then what is already selected becomes apparent e.focus(); } } }); document.getElementById("select_userjs_button").addEventListener("click", function() { selectTextAreaContents("box_3_userjs"); }); document.getElementById("select_other_button").addEventListener("click", function() { selectTextAreaContents("box_4_other"); }); // functions_panel_textarea document.getElementById("select_functions_button").addEventListener("click", function() { selectTextAreaContents("functions_panel_textarea"); }); // load (hidden input button) document.getElementById('load_template_input').addEventListener('change', function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { loadLocalFile("load_template_input", "box_1_template"); } }, false); document.getElementById('load_overrides_input').addEventListener('change', function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { loadLocalFile("load_overrides_input", "box_2_overrides"); } }, false); document.getElementById('load_userjs_input').addEventListener('change', function() { loadLocalFile("load_userjs_input", "box_3_userjs"); }, false); document.getElementById('load_other_input').addEventListener('change', function() { loadLocalFile("load_other_input", "box_4_other"); }, false); // load/save (select button) document.getElementById("loadsave_template_select").addEventListener("change", function() { loadButtonAction(this, "load_template_input", "box_1_template", "user-template.js"); }); document.getElementById("loadsave_overrides_select").addEventListener("change", function() { loadButtonAction(this, "load_overrides_input", "box_2_overrides", "user-overrides.js"); }); document.getElementById("loadsave_userjs_select").addEventListener("change", function() { loadButtonAction(this, "load_userjs_input", "box_3_userjs", "user.js"); }); document.getElementById("loadsave_other_select").addEventListener("change", function() { updateDateTimeStampVariable(); loadButtonAction(this, "load_other_input", "box_4_other", "user-other-" + date_time_stamp + ".js"); }); // clear document.getElementById("clear_template_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { clearTextAreaContents("box_1_template"); } }); document.getElementById("clear_overrides_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { clearTextAreaContents("box_2_overrides"); } }); document.getElementById("clear_userjs_button").addEventListener("click", function() { clearTextAreaContents("box_3_userjs"); }); document.getElementById("clear_other_button").addEventListener("click", function() { clearTextAreaContents("box_4_other"); }); // ************** // boxdiv buttons // ************** document.getElementById("div_1_template_button").addEventListener("click", function() { toggleTemplateDiv(); }); document.getElementById("div_2_overrides_button").addEventListener("click", function() { toggleOverridesDiv(); }); document.getElementById("div_3_userjs_button").addEventListener("click", function() { toggleUserjsDiv(); }); document.getElementById("div_4_other_button").addEventListener("click", function() { toggleOtherDiv(); }); document.getElementById("all_button").addEventListener("click", function() { if (document.getElementById("div_1_template").style.float == "left") { toggleAllDivVertical(); } else { toggleAllDiv(); } }); // ************************************* // action buttons // ************************************* // arkenfox view document.getElementById('view_arkenfox_button').addEventListener('click', function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { toggleTemplateDiv(); downloadFile( "https://raw.githubusercontent.com/arkenfox/user.js/master/user.js", "box_1_template"); setTimeout(function(){ document.getElementById("tableview_template_button").click(); }, 1000); } }, false); // arkenfox load document.getElementById('load_arkenfox_button').addEventListener('click', function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" ) { toggleTemplateDiv(); downloadFile( "https://raw.githubusercontent.com/arkenfox/user.js/master/user.js", "box_1_template"); } }, false); // compare document.getElementById("compare_select").addEventListener("change", function() { // user chooses the textarea input boxes for compare option // get the choice var choice = this.value; // make the select element show title (first option) this.selectedIndex = 0; userjsCompareLauncher(choice); }); // append document.getElementById("append_button").addEventListener("click", function() { // output user.js (box3) = user-template.js (box1) append user-overrides.js (box2) if ( !(document.getElementById("box_1_template").value == "") && !(document.getElementById("box_2_overrides").value == "") ) { userjsAppend("box_1_template", "box_2_overrides", "box_3_userjs"); if (!checkIfAllButtonSelected()) { toggleUserjsDiv() } readOnlySelectTextAreaContents("box_3_userjs"); } }); // skeleton document.getElementById("skeleton_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_1_template").value == "") ) { userjsReduce("box_1_template", "box_2_overrides", true); if (!checkIfAllButtonSelected()) { toggleOverridesDiv() } readOnlySelectTextAreaContents("box_2_overrides"); } }); // collect document.getElementById("collect_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration != "line-through" && !(document.getElementById("box_1_template").value == "") ) { toggleOverridesDiv(); // put collect mode on (line through buttons that are disabled) for (const id of [ "view_template_button", "view_overrides_button", "view_userjs_button", "view_other_button", "tableview_template_button", "tableview_overrides_button", "tableview_userjs_button", "tableview_other_button", "clear_template_button", "clear_overrides_button", "view_arkenfox_button", "load_arkenfox_button", "compare_select", "skeleton_button", "collect_button", "byvalue_button", "togroup_button" ] ) { document.getElementById(id).style.textDecoration = "line-through"; } document.getElementById("select_overrides_button").innerHTML = "Select+"; // unhide end collect button document.getElementById("endcollect_button").style.color = "#FF0000"; /*Red*/ document.getElementById("endcollect_button").style.backgroundColor = "#FFFF00"; /*Yellow*/ document.getElementById("endcollect_button").style.display = "block"; document.getElementById("endcollect_br").style.display = "block"; // shrink the actions_panel, and focus it on Box 2 // make collect_mode_pad visible (which makes the user.js HTML // lower, so the actions_panel will not obscure user.js HTML) document.getElementById("collect_mode_pad").style.display = "block"; document.getElementById("actions_panel").style.maxHeight = "23%"; document.getElementById("box_2_overrides").scrollIntoView(); toggleTextAreaReadOnly("box_2_overrides", false); userjsViewer("box_1_template", true); // make the user.js HTML clickable (adds 100's of Event Listeners) addEventListenersForCollectMode(); } }); // end collect document.getElementById("endcollect_button").addEventListener("click", function() { if ( document.getElementById("collect_button").style.textDecoration == "line-through" ) { // put collect mode off // remove the user.js HTML clickable (removes 100's of Event Listeners) removeEventListenersForCollectMode(); for (const id of [ "view_template_button", "view_overrides_button", "view_userjs_button", "view_other_button", "tableview_template_button", "tableview_overrides_button", "tableview_userjs_button", "tableview_other_button", "clear_template_button", "clear_overrides_button", "view_arkenfox_button", "load_arkenfox_button", "compare_select", "skeleton_button", "collect_button", "byvalue_button", "togroup_button" ] ) { document.getElementById(id).style.textDecoration = null; } document.getElementById("select_overrides_button").innerHTML = "Select"; // unhide end collect button document.getElementById("endcollect_button").style.color = null; document.getElementById("endcollect_button").style.backgroundColor = null; document.getElementById("endcollect_button").style.display = "none"; document.getElementById("endcollect_br").style.display = "none"; // make actions_panel usual size and hide collect_mode_pad document.getElementById("collect_mode_pad").style.display = "none"; document.getElementById("actions_panel").style.maxHeight = "75%"; // display the collected values userjsViewer("box_2_overrides"); toggleOverridesDiv(); readOnlySelectTextAreaContents("box_2_overrides"); } }); // reduce document.getElementById("reduce_button").addEventListener("click", function() { if (!(document.getElementById("box_1_template").value == "")) { userjsReduce("box_1_template", "box_4_other"); if (!checkIfAllButtonSelected()) { toggleOtherDiv() } readOnlySelectTextAreaContents("box_4_other"); } }); // clean document.getElementById("clean_button").addEventListener("click", function() { if ( !(document.getElementById("box_3_userjs").value == "") && !(document.getElementById("box_4_other").value == "") ) { prefsjsClean("box_3_userjs", "box_4_other"); if (!checkIfAllButtonSelected()) { toggleOtherDiv() } readOnlySelectTextAreaContents("box_4_other"); } }); // byvalue document.getElementById("byvalue_button").addEventListener("click", function() { if ( (document.getElementById("collect_button").style.textDecoration != "line-through") && !(document.getElementById("box_3_userjs").value == "") ) { userjsToValueGroups("box_3_userjs", "box_4_other"); if (!checkIfAllButtonSelected()) { toggleOtherDiv() } readOnlySelectTextAreaContents("box_4_other"); userjsViewer("box_4_other"); toggleGroupsPanel(true); } }); // togroup document.getElementById("togroup_button").addEventListener("click", function() { if ( (document.getElementById("collect_button").style.textDecoration != "line-through") && !(document.getElementById("box_4_other").value == "") ) { convertListToGroup("box_4_other"); if (!checkIfAllButtonSelected()) { toggleOtherDiv() } readOnlySelectTextAreaContents("box_4_other"); userjsViewer("box_4_other"); setExpandAll(true) toggleGroupsPanel(true); } }); // base64 encode/decode etc document.getElementById("encode_select").addEventListener("change", function() { var choice = this.selectedIndex; this.selectedIndex = 0; var e = document.getElementById("box_4_other"); if (!(e.value == "")) { switch (choice) { case 1: // base64 encode text (and wrap encoded lines after 76 characters) e.value = b64EncodeUnicode(e.value).replace(/.{76}/g, "$&\n"); break; case 2: // base64 encode text e.value = b64EncodeUnicode(e.value); break; case 3: // base64 decode text e.value = b64DecodeUnicode(e.value); break; case 4: e.value = encodeURIComponent(e.value); break; case 5: e.value = decodeURIComponent(e.value); break; case 6: e.value = encodeURI(e.value); break; case 7: e.value = decodeURI(e.value); break; case 8: e.value = RegExp.escape(e.value); break; } if (!checkIfAllButtonSelected()) { toggleOtherDiv() } readOnlySelectTextAreaContents("box_4_other"); } }); // functions for about:config document.getElementById("functions_button").addEventListener("click", function() { return_to_panel = "actions_panel"; toggleFunctionsPanel(true); }); // links document.getElementById("links_button").addEventListener("click", function() { return_to_panel = "actions_panel"; toggleLinksPanel(true); }); // help document.getElementById("help_button").addEventListener("click", function() { return_to_panel = "actions_panel"; toggleHelpPanel(true); }); } /* end function userjstoolSetupEventListeners */ // ************************************* // userjstoolStart // ************************************* /* ask for user.js input (or use content in body) (or load) */ function userjstoolStart() { userjstoolSetupInital(); userjstoolSetupEventListeners(); userjstoolSetupURLvariables(); toggleActionsPanel(true); toggleTemplateDiv(); var timeoutDelay = false; // gets set true if any content is auto-loaded // if user.js content saved in this HTML file (the divs at the bottom) // the userjstoolSetupInital function placed that content into a textarea if (document.getElementById("box_4_other").value != "") { toggleOtherDiv(); document.getElementById("view_other_button").click(); } else if (document.getElementById("box_3_userjs").value != "") { toggleUserjsDiv(); document.getElementById("view_userjs_button").click(); } else if (document.getElementById("box_1_template").value != "") { toggleTemplateDiv(); document.getElementById("view_template_button").click(); } else if (document.getElementById("box_2_overrides").value != "") { toggleOverridesDiv(); document.getElementById("view_overrides_button").click(); } // if load parameters in URL if (/^\?(a|av|at|g|gv|gt)($|&)/.test(location.search)) { toggleTemplateDiv(); downloadFile( "https://raw.githubusercontent.com/arkenfox/user.js/master/user.js", "box_1_template"); timeoutDelay = true; } if (getURLVariable("load4") != "" && getURLVariable("load4") != null) { toggleOtherDiv(); downloadFile(getURLVariable("load4"), "box_4_other"); timeoutDelay = true; } if (getURLVariable("load3") != "" && getURLVariable("load3") != null) { toggleUserjsDiv(); downloadFile(getURLVariable("load3"), "box_3_userjs"); timeoutDelay = true; } if (getURLVariable("load2") != "" && getURLVariable("load2") != null) { toggleOverridesDiv(); downloadFile(getURLVariable("load2"), "box_2_overrides"); timeoutDelay = true; } if (getURLVariable("load1") != "" && getURLVariable("load1") != null) { toggleTemplateDiv(); downloadFile(getURLVariable("load1"), "box_1_template"); timeoutDelay = true; } // NOTE: // if the code above loaded the file, the display does not render // before the next code lines can see the text box has contents, // so set a delay, which mostly works, and if it fails it only // breaks auto view and the user can click a view button anyway setTimeout(function(){ if (/^\?(av|gv)($|&)/.test(location.search)) { document.getElementById("view_template_button").click(); } else if (/^\?(at|gt)($|&)/.test(location.search)) { document.getElementById("tableview_template_button").click(); } // when box parameter in URL switch (getURLVariable("box")) { case "0": toggleActionsPanel(false); break; case "1": toggleActionsPanel(true); toggleTemplateDiv(); break; case "2": toggleActionsPanel(true); toggleOverridesDiv(); break; case "3": toggleActionsPanel(true); toggleUserjsDiv(); break; case "4": toggleActionsPanel(true); toggleOtherDiv(); break; case "a": toggleActionsPanel(true); toggleAllDiv(); break; case "v": toggleActionsPanel(true); toggleAllDivVertical(); } // make the textarea input boxes read only if they have content toggleTextAreaReadOnly("box_1_template", true); toggleTextAreaReadOnly("box_2_overrides", true); toggleTextAreaReadOnly("box_3_userjs", true); toggleTextAreaReadOnly("box_4_other", true); handleActionURLparameter(); if (getURLVariable("jump") != "" && getURLVariable("jump") != null) { expandSectionWithFirstInstanceOfText(getURLVariable("jump")); } }, timeoutDelay ? 1000 : 0); } /* end function userjstoolStart */ </script> </head> <!-- ##################################### ##################################### body ##################################### ##################################### --> <body onload="userjstoolStart()"> <!-- hidden heading (H1/H3/DL tags for bookmarks import compatibility) --> <div class="hidden"><H1>Bookmarks Menu</H1></div><DL> <!-- >>>> date is inserted here later <<<< --> <div id="hiddendate" class="hidden"><H3>--userjs_</H3></div><DL> <!-- ************************************* top_buttons_bar ************************************* --> <div id="top_buttons_bar"> <!-- index_select --> <!-- the first option is used as a title/label --> <select class="controls borders top_buttons" id="index_select" title="Go to section"> <!-- >>>> section headings (between here) are replaced later <<<< --> <option value="" disabled selected hidden>▾Index</option> <option>(TOP) / Introduction</option> <!-- >>>> section headings (between here) are replaced later <<<< --> </select> <!-- groups_button --> <button type="button" class="controls borders top_buttons" id="groups_button" title="Show/hide about:config Groups panel" >Groups</button> <!-- size_select --> <!-- the first option is used as a title/label and changes to show the current font size percent --> <select class="controls borders top_buttons" id="size_select" title="Change font size"> <option value="" disabled selected hidden>▾100%</option> <option>-10%</option> <option>+10%</option> <option>50%</option> <option>75%</option> <option>100%</option> <option>125%</option> <option>150%</option> <option>175%</option> <option>200%</option> <option>250%</option> <option>300%</option> </select> <!-- menu_select --> <!-- some of these option names (the prefix symbol) will alter when toggled the menu_select addEventListener matches option name keywords not indexes the first option is used as a title/label options are prefixed with unicode characters: ◫ WHITE SQUARE WITH VERTICAL BISECTING LINE (not a toggle) ▢ WHITE SQUARE WITH ROUNDED CORNERS (off/false) ▣ WHITE SQUARE CONTAINING BLACK SMALL SQUARE (on/true)   (blank/space) as leading/multi-spaces are lost … symbol for '...' (not used, looks too small) ⁝ TRICOLON (vertical ...) (not used) --> <select id="menu_select" class="controls borders top_buttons" title="Select menu option"> <option value="" disabled selected hidden>▾Menu</option> <option>◫  Collapse all</option> <option>◫  Expand all</option> <option>◫  Collapse current/headings</option> <option value="" disabled></option> <option>▢  [Actions] (input panel)</option> <option>▢  [Groups] (about:config search)</option> <option>▢  [Links] (template/profiles/etc)</option> <option>▢  [about:config Functions]</option> <option>▢  [Help/Info]</option> <option value="" disabled></option> <option>▢  Wrap text</option> <option>▢  Prefix about:config links</option> <option>▢  Function about:config links</option> <option value="" disabled></option> <option>▢  Expand all on view</option> <option>▢  Show Groups on view</option> <option>▢  View+ style on view</option> <option value="" disabled></option> <option>▢  Theme: default</option> <option>▢  Theme: dark</option> <option>▢  Theme: light</option> <option>◫  Other themes</option> </select> <button type="button" id="back_button" class="controls borders top_buttons" title="Close panel" onclick="backButton();"><b><</b>Back</button> </div><!-- end: top_buttons_bar --> <!-- ************************************* top of body / view_area ************************************* --> <br><br><br><br> <div id="collect_mode_pad"><br><br><br><br><br><br><br><br><br><br><br><br><br> <br>Note: a very basic overrides collector, click value/"user_pref" to toggle.<br> You can also edit box 2 above, and don't forget to save what you do.<br><br></div> <div id="view_area"> <!-- >>>> section contents (between here) are replaced later <<<< --> <button type="button" class="controls borders heading_buttons" id="TOP-Introduction">(TOP) / Introduction</button> <div class="content"> Please add user.js content under [Menu]>[Actions], then use [View], [Table View], or [Compare]. </div> <!-- >>>> section contents (between here) are replaced later <<<< --> </div><!-- end: view_area --> <!-- ************************************* actions_panel ************************************* --> <div id="actions_panel" class="panels borders"> <span class="close_panel_x" onclick="toggleActionsPanel();" title="Close [Actions] panel">×</span> <!-- hidden heading (H1/H3/DL tags for bookmarks import compatibility) --> <div class="hidden"><H3>[Actions]</H3></div> <b>[Actions] (Firefox user.js view, compare and more)</b> ([X] closes)<br> (Please see "Hints" below, scroll down)<br><br> <!-- actions_buttons_bar --> <div id="actions_buttons_bar"><!-- --><button type="button" class="controls borders" id="div_1_template_button" title="Display Template box (eg arkenfox user.js)">Box 1<br>Template</button><!-- --><button type="button" class="controls borders" id="div_2_overrides_button" title="Display user-overrides.js box">Box 2<br>Overrides</button><!-- --><button type="button" class="controls borders" id="div_3_userjs_button" title="Display user.js box">Box 3<br>user.js</button><!-- --><button type="button" class="controls borders" id="div_4_other_button" title="Display Other box (eg prefs.js/etc)">Box 4<br>Other</button><!-- --><button type="button" class="controls borders" id="all_button" title="Show all boxes">All</button><!-- --></div><!-- end: actions_buttons_bar --> <!-- div_1_template --> <div id="div_1_2"> <div id="div_1_template"><table><tr><td class="td1_actions"> <b>Box 1</b><!-- --> <div id="box_1_template_ro">▢ro</div><br> <button type="button" class="controls borders" id="view_template_button" title="View box content with HTML, links, etc">View</button><br> <button type="button" class="controls borders" id="tableview_template_button" title="View box content in a table (for arkenfox user.js)">Table View</button><br> <select class="controls borders" id="loadsave_template_select" title="Select a file and load contents into the box"> <option value="" disabled selected hidden>▾Load/Save</option> </select><br> <button type="button" class="controls borders" id="select_template_button" title="Select all text in the box">Select</button><br> <button type="button" class="controls borders" id="clear_template_button" title="Clear the text in the box">Clear</button> <input class="hidden controls borders" type="file" multiple="multiple" name="file" accept=".js" id="load_template_input" /> </td><td class="td2_actions"> <a target="_blank" rel="external noopener noreferrer" class="local_folder" href="user-template.js">Template</a> eg <a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js">arkenfox</a> <textarea id="box_1_template" autofocus="true" ondrop="dropHandler1(event);" ondragover="dragOverHandler(event);" placeholder=" Add user.js template here (Use [Load] button, drag/drop or paste) Then use [View], [Table View], [Compare], or another action button" ></textarea> </td></tr></table></div> <!-- div_2_overrides --> <div id="div_2_overrides" class="hidden"><table><tr><td class="td1_actions"> <b>Box 2</b><!-- --> <span id="box_2_overrides_ro">▢ro</span><br> <!-- End Collect button is hidden until needed --> <button type="button" class="controls borders" id="endcollect_button" title="End the point and click overrides collection" >End Collect</button><br id="endcollect_br"> <button type="button" class="controls borders" id="view_overrides_button" title="View box content with HTML, links, etc">View</button><br> <button type="button" class="controls borders" id="tableview_overrides_button" title="View box content in a table (for arkenfox user.js)">Table View</button><br> <select class="controls borders" id="loadsave_overrides_select" title="Select a file and load contents into the box"> <option value="" disabled selected hidden>▾Load/Save</option> </select><br> <button type="button" class="controls borders" id="select_overrides_button" title="Select all text in the box">Select</button><br> <button type="button" class="controls borders" id="clear_overrides_button" title="Clear the text in the box">Clear</button><br> <input class="hidden controls borders" type="file" multiple="multiple" name="file" accept=".js" id="load_overrides_input" /> </td><td class="td2_actions"> <a target="_blank" rel="external noopener noreferrer" class="local_folder" href="user-overrides.js">user-overrides.js</a> <textarea id="box_2_overrides" ondrop="dropHandler2(event);" ondragover="dragOverHandler(event);" placeholder=" Add user-overrides.js here (Use [Load] button, drag/drop or paste) (or used for action button input/output)" ></textarea> </td></tr></table></div> </div> <!-- div_3_userjs --> <div id="div_3_4"> <div id="div_3_userjs" class="hidden"><table><tr><td class="td1_actions"> <b>Box 3</b><!-- --> <span id="box_3_userjs_ro">▢ro</span><br> <button type="button" class="controls borders" id="view_userjs_button" title="View box content with HTML, links, etc">View</button><br> <button type="button" class="controls borders" id="tableview_userjs_button" title="View box content in a table (for arkenfox user.js)">Table View</button><br> <select class="controls borders" id="loadsave_userjs_select" title="Select a file and load contents into the box"> <option value="" disabled selected hidden>▾Load/Save</option> </select><br> <button type="button" class="controls borders" id="select_userjs_button" title="Select all text in the box">Select</button><br> <button type="button" class="controls borders" id="clear_userjs_button" title="Clear the text in the box">Clear</button><br> <input class="hidden controls borders" type="file" multiple="multiple" name="file" accept=".js" id="load_userjs_input" /> </td><td class="td2_actions"> <a target="_blank" rel="external noopener noreferrer" class="local_folder" href="user.js">user.js</a> <textarea id="box_3_userjs" ondrop="dropHandler3(event);" ondragover="dragOverHandler(event);" placeholder=" Add user.js here (Use [Load] button, drag/drop or paste) (or used for action button input/output)" ></textarea> </td></tr></table></div> <!-- div_4_other --> <div id="div_4_other" class="hidden"><table><tr><td class="td1_actions"> <b>Box 4</b><!-- --> <span id="box_4_other_ro">▢ro</span><br> <button type="button" class="controls borders" id="view_other_button" title="View box content with HTML, links, etc">View</button><br> <button type="button" class="controls borders" id="tableview_other_button" title="View box content in a table (for arkenfox user.js)">Table View</button><br> <select class="controls borders" id="loadsave_other_select" title="Select a file and load contents into the box"> <option value="" disabled selected hidden>▾Load/Save</option> </select><br> <button type="button" class="controls borders" id="select_other_button" title="Select all text in the box">Select</button><br> <button type="button" class="controls borders" id="clear_other_button" title="Clear the text in the box">Clear</button><br> <input class="hidden controls borders" type="file" multiple="multiple" name="file" accept=".js" id="load_other_input" /> </td><td class="td2_actions"> <a target="_blank" rel="external noopener noreferrer" class="local_folder" href=".">Other</a><!-- -->/<a target="_blank" rel="external noopener noreferrer" class="local_folder" href="prefs.js">prefs.js</a> <textarea id="box_4_other" ondrop="dropHandler4(event);" ondragover="dragOverHandler(event);" placeholder=" Add other/user.js here (Use [Load] button, drag/drop or paste) (or used for action button input/output)" ></textarea> </td></tr></table></div> </div> <!-- actions_buttons_div --> <div id="actions_buttons_div"><!-- --><button type="button" class="controls borders" id="view_arkenfox_button" title="View current arkenfox user.js in a table (Load in Box 1)">View arkenfox >1</button><!-- --><button type="button" class="controls borders" id="load_arkenfox_button" title="Load current arkenfox user.js into Box 1">Load arkenfox >1</button><!-- --><select class="controls borders" id="compare_select" title="Compare the user_pref of 2 boxes"> <option value="" disabled selected hidden>▾Compare</option> <option>Compare box 1 & 1   A-Z</option> <option>Compare box 1 & 2   ◦</option> <option>Compare box 1 & 3   ◦</option> <option>Compare box 1 & 4   ◦</option> <option>Compare box 2 & 1</option> <option>Compare box 2 & 2   A-Z</option> <option>Compare box 2 & 3   ◦</option> <option>Compare box 2 & 4   ◦</option> <option>Compare box 3 & 1</option> <option>Compare box 3 & 2</option> <option>Compare box 3 & 3   A-Z</option> <option>Compare box 3 & 4   ◦</option> <option>Compare box 4 & 1</option> <option>Compare box 4 & 2</option> <option>Compare box 4 & 3</option> <option>Compare box 4 & 4   A-Z</option> </select><!-- --><button type="button" class="controls borders" id="append_button" title="Append/apply overrides onto template to form user.js" >Append 1+2>3</button><!-- --><button type="button" class="controls borders" id="skeleton_button" title="Create a user-overrides.js skeleton" >Skeleton 1>2</button><!-- --><button type="button" class="controls borders" id="collect_button" title="Point and click overrides collector" >Collect 1>+2</button><!-- --><button type="button" class="controls borders" id="reduce_button" title="Reduce user.js to just user_pref lines" >Reduce 1>4</button><!-- --><button type="button" class="controls borders" id="clean_button" title="Remove user.js user_pref from another .js" >Clean 4-3>4</button><!-- --><button type="button" class="controls borders" id="byvalue_button" title="Group user_pref by value (eg for Android about:config)" >By Value 3>4</button><!-- --><button type="button" class="controls borders" id="togroup_button" title="Preference list to about:config?filter= group" >To Group 4>4</button><!-- --><select class="controls borders" id="encode_select" title="Encode/Decode text (Base64, URI)"> <option value="" disabled selected hidden>▾Encode 4>4</option> <option>Encode text to Base64 (multi line)</option> <option>Encode text to Base64</option> <option>Decode Base64 to text</option> <option>Encode URI Component</option> <option>Decode URI Component</option> <option>Encode URI</option> <option>Decode URI</option> <option>Escape for RegExp</option> </select><!-- --><button type="button" class="controls borders" id="functions_button" title="Functions for about:config (find (filter/list)/reset/set)" >a:c Functions</button><!-- --><button type="button" class="controls borders" id="links_button" title="Display some useful links (eg: arkenfox user.js, auto-load, profile folders)" >Links</button><!-- --><button type="button" class="controls borders" id="help_button" >Help/Info</button> <br><br><br>Hints:<br> <ul> <br><li><a target="_blank" rel="external noopener noreferrer" href="?at" >To view the current arkenfox user.js in a table</a> <br>Click the [View arkenfox] button, or use the above link.</li> <br><li><a target="_blank" rel="external noopener noreferrer" href="?a&action=collect" >To collect/create Overrides from arkenfox user.js</a> <br>Click [Load arkenfox] then [Collect], or use the above link.</li> <br><li><b>What do the buttons do?</b> <br>Hover mouse over buttons for their info, or click [Help/Info].</li> <br><li><b>How to return here to [Actions]?</b> <br>Click [Menu] choose [Actions], or try the [<b><</b>Back] button.</li> <br><li><b>View or compare user.js</b> <br>Place user.js text into the box(es) above using the [Load] button (or drag/drop, copy/paste) then click [View], [Table View] or [Compare].</li> <br><li><b>To append your user-overrides.js</b> <br>Place the user.js template in Box 1 (or click [Load arkenfox]), place your overrides in Box 2, then click [Append], resulting user.js will be in Box 3.</li> <br><li><b>What are the numbers on some buttons?</b> <br>It indicates "input>output" boxes, contents of the output box are usually replaced, [Collect] amends the output box.</li> <br><li><b>[Index] = section jump, [Groups] = about:config search patterns</b> <br>These are updated after [View]/[Table View]/[Compare] are used.</li> <br><li><a target="_blank" rel="external noopener noreferrer" href="https://raw.githubusercontent.com/icpantsparti/firefox-user.js-tool/master/userjs-tool-aboutconfig-functions.js" >To get more out of about:config (eg: find, filter, list, save to file, etc)</a> <br>Click [a:c Functions], or use the above link.</li> <br><li><a target="_blank" rel="external noopener noreferrer" href="?at&groups=true" >For searching arkenfox user.js preferences in about:config</a> <br>Click [View arkenfox] then [Groups], or use the above link.</li> <br><li><a target="_blank" rel="external noopener noreferrer" href="?box=a&action=togroup&load4=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/scratchpad-scripts/troubleshooter.js" >For searching arkenfox troubleshooter.js preferences in about:config</a> <br>Place troubleshooter.js in Box 4 click [To Group], or use the above link.</li> </ul> <br><br><br><br><br> </div><!-- end: actions_buttons_div --> </div><!-- end: actions_panel --> <!-- ************************************* groups_panel ************************************* --> <div id="groups_panel" class="panels borders"> <span class="close_panel_x" onclick="toggleGroupsPanel();" title="Close [Groups] panel">×</span> <!-- hidden heading (H1/H3/DL tags for bookmarks import compatibility) --> <div class="hidden"><H3>[Groups] (copy/paste into about:config search box)</H3></div> <b>[Groups] (copy/paste into about:config search box)</b> ([X] closes)<br> (these links: right click, a, hold Ctrl: T, V, Enter, V)<br> (if bookmarked: right click, c, left click, Ctrl+V) <br><br> <!-- >>>> user pref groups (between here) are replaced later <<<< --> <div id="groups_container"> Please add user.js content under [Menu]>[Actions], then use [View], [Table View], or [Compare]. </div> <!-- >>>> user pref groups (between here) are replaced later <<<< --> <br><br><br><br><br> </div><!-- end: groups_panel --> <!-- ************************************* links_panel ************************************* --> <div id="links_panel" class="panels borders"> <span class="close_panel_x" onclick="toggleLinksPanel();" title="Close [Links] panel">×</span> <!-- hidden heading (H1/H3/DL tags for bookmarks import compatibility) --> <div class="hidden"><H3>[Links]</H3></div> <b>[Links]</b> ([X] closes) <br><br> When choosing a user.js template you need to consider if it is suitable, good quality, and up to date. (At your own risk)<br> <br><br> user.js template example <table class="table_links"> <tr><td class="td1_links"> <a target="_blank" rel="external noopener noreferrer" href="https://raw.githubusercontent.com/arkenfox/user.js/master/user.js" >arkenfox user.js #</a> <br><br>(read their wiki) (listed on privacytools.io) <br><br><a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js/issues/123" >arkenfox user.js deprecated-removed-legacy prefs (list)</a> </td><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js" ><span class="hidden">__arkenfox user.js </span>project page</a> <br><br><a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js/wiki" ><span class="hidden">__arkenfox user.js </span>wiki</a> <br><br><a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js/issues?q=sort%3Aupdated-desc" ><span class="hidden">__arkenfox user.js </span>issues</a> </td></tr> </table> <!-- note about URL parameters --> <br><br> Auto-loading (also see [Menu]>[Help/Info]>Initial states) <table class="table_links"> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?a&box=a" >Load current arkenfox user.js</a> (or specific <a target="_blank" rel="external noopener noreferrer" href="?box=a&load1=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/user.js" ><span class="hidden">Load current arkenfox user.js/</span>URL</a>) </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?at" >View current arkenfox user.js in a table</a> (or specific <a target="_blank" rel="external noopener noreferrer" href="?box=a&action=table1&load1=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/user.js" ><span class="hidden">View current arkenfox user.js in a table/</span>URL</a>) </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?av" >View current arkenfox user.js with styling</a> (or specific <a target="_blank" rel="external noopener noreferrer" href="?box=a&action=view1&load1=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/user.js" ><span class="hidden">View current arkenfox user.js with styling/</span>URL</a>) </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?a&action=collect" >Overrides collector (using arkenfox)</a> (by clicking values/"user_pref") </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?load2=data:text/plain;base64,Ly8gdGhpcyB0ZXh0IHdhcyBpbiB0aGUgbGluay9ib29rbWFyayAodXNpbmcgQmFzZTY0KQovLyAoZG8gbm90IHB1dCBhbnl0aGluZyBwcml2YXRlKQoKLy8gdXNlci1vdmVycmlkZXMuanMKLy8vLyAtLS0gYWRkLW92ZXJyaWRlLWNvbW1lbnQgLS0tCi8qKiogVElUTEUgKioqLwp1c2VyX3ByZWYoInByZWYubmFtZSIsIG51bGwpOwovLy8vIC0tLSBjb21tZW50LW91dCAtLS0gJ3ByZWYubmFtZScK" >load some non private text (?load2=data:text/plain;base64,...)</a> </td></tr> </table> <!-- example links for preference groups --> <br><br> Auto-load about:config Groups (for about:config search box to display multiple preferences) <table class="table_links"> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?at&groups=true" >Groups from arkenfox user.js by section</a> (or specific <a target="_blank" rel="external noopener noreferrer" href="?groups=true&box=a&action=table1&load1=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/user.js" ><span class="hidden">Groups from arkenfox user.js by section/</span>URL</a>) </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?box=a&action=byvalue&load3=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/user.js" >Groups from arkenfox user.js by value</a> </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?box=a&action=togroup&load4=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/scratchpad-scripts/troubleshooter.js" >Groups from arkenfox troubleshooter.js</a> </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?box=a&action=togroup&load4=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/scratchpad-scripts/arkenfox-clear-deprecated.js" >Groups from arkenfox-clear-deprecated.js</a> </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?box=a&action=togroup&load4=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/scratchpad-scripts/arkenfox-clear-removed.js" >Groups from arkenfox-clear-removed.js</a> </td></tr> <tr><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="?box=a&action=togroup&load4=%68ttps://raw.githubusercontent.com/arkenfox/user.js/master/scratchpad-scripts/arkenfox-clear-RFP-alternatives.js" >Groups from arkenfox-clear-RFP-alternatives.js</a> </td></tr> </table> <!-- ***** profile locations ***** --> <br><br> Profile Locations (see <a target="_blank" rel="external noopener noreferrer" href="about:profiles">about:profiles</a> in a new tab) <table class="table_links"> <tr><td class="td1_links"> file:///C:/Users/USERNAME/AppData/Roaming/Mozilla/Firefox/Profiles </td><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="file:///C:/Users/USERNAME/AppData/Roaming/Mozilla/Firefox/Profiles" ><span class="hidden">Firefox profile: </span>Windows</a> </td></tr> <tr><td class="td1_links"> file:///data/data/org.mozilla.firefox/files/mozilla </td><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="file:///data/data/org.mozilla.firefox/files/mozilla" ><span class="hidden">Firefox profile: </span>Android</a> </td></tr> <tr><td class="td1_links"> file:///data/data/org.mozilla.fenix/files/mozilla </td><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="file:///data/data/org.mozilla.fenix/files/mozilla" ><span class="hidden">Firefox profile: </span>Android Nightly</a> </td></tr> <tr><td class="td1_links"> file:///Users/USERNAME/Library/Application Support/Firefox/Profiles </td><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="file:///Users/USERNAME/Library/Application%20Support/Firefox/Profiles" ><span class="hidden">Firefox profile: </span>OS X</a> </td></tr> <tr><td class="td1_links"> file:///home/USERNAME/.mozilla/firefox </td><td class="td2_links"> <a target="_blank" rel="external noopener noreferrer" href="file:///home/USERNAME/.mozilla/firefox" ><span class="hidden">Firefox profile: </span>Linux</a> </td></tr> </table> <br><br><br><br><br></div><!-- end: links_panel --> <!-- ************************************* functions_panel ************************************* --> <div id="functions_panel" class="panels borders"> <span class="close_panel_x" onclick="toggleFunctionsPanel();" title="Close [Functions] panel">×</span> <b>[about:config Functions]</b> ([X] closes)<br><br> <button type="button" class="controls borders" id="select_functions_button" title="Select all text in the box">Select</button> <br><br><br><textarea id="functions_panel_textarea" autofocus="true"></textarea> <br><br><br><br><br></div><!-- end: functions_panel --> <!-- ************************************* help_panel (visible when JavaScript is off) ************************************* --> <div id="help_panel" class="panels borders"> <span class="close_panel_x" onclick="toggleHelpPanel();" title="Close [Help] panel">×</span> <!-- hidden heading (H1/H3/DL tags for bookmarks import compatibility) --> <div class="hidden"><H3>[Help/Info]</H3></div> <b>[Help/Info] (userjs-tool.html)</b> ([X] closes)<br><br> <!-- javascript off message --> <div id="help_panel_nojs_div" class="borders"> Hi! This requires JavaScript for dynamically rendering the HTML interface, interactions and calculations.<br> <br> If you see this message perhaps JavaScript is off, or controlled by an extension such as: uBlock Origin, uMatrix, NoScript.<br> </div><hr> <!-- version/links --> <br> Interactive view, compare, and more for Firefox user.js (eg arkenfox user.js).<br> <br> Version : 2021.03.11 (alpha/experimental)<br> Project : <a target="_blank" rel="external noopener noreferrer" href="https://github.com/icpantsparti/firefox-user.js-tool" >firefox-user.js-tool (GitHub)</a> (updates/issues/etc)<br> File : <a target="_blank" rel="external noopener noreferrer" href="https://raw.githubusercontent.com/icpantsparti/firefox-user.js-tool/master/userjs-tool.html" >userjs-tool.html (<span class="hidden">file/</span>GitHub)</a><br> On-line : <a target="_blank" rel="external noopener noreferrer" href="https://icpantsparti.github.io/firefox-user.js-tool/userjs-tool.html" >userjs-tool.html (<span class="hidden">on-line/</span>GitHub Pages)</a><br> License : <a target="_blank" rel="external noopener noreferrer" href="https://raw.githubusercontent.com/icpantsparti/firefox-user.js-tool/master/LICENSE" ><span class="hidden">firefox-user.js-tool - </span>MIT License (GitHub)</a><br> Disclaimer: Use with care at your own risk, and verify any results <br><br>This is a single .html file (HTML/CSS/JavaScript) with no external dependency. Open on-line or save for off-line use. <br><br><hr> <!-- Introduction --> <br><details><summary>Introduction</summary> <div class="indentdiv"><br> Display a Mozilla Firefox user.js settings file contents in your Firefox browser, with:<br> <ul> <br><li>highlighting, links, themes*, re-size, wrap, about:config links/regex/groups</li> <br><li>expanding sections, and index to go to sections (with compatible user.js projects)</li> <br><li>compare preferences in two user.js, in a table format with order/layout options and bold cell border around differences</li> <br><li>actions including: user-overrides.js* append* (with comment-out*), point and click overrides collector, skeleton, prefs.js cleaner*, group by values</li> <br><li>load/save, drag/drop, or copy/paste user.js files (can load from some on-line URLs too)</li> <br><li>functions for find (filter/list)/reset/set on about:config Web Console (Firefox/forks/Thunderbird/SeaMonkey)</li> <br><li>single .html file (HTML/CSS/JavaScript) with no external dependency</li> <br><li>open userjs-tool.html on-line or save for off-line use</li> </ul> <br>(*<a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js" >arkenfox user.js 💎</a> inspired, see "Acknowledgments" section below. Please visit them and read their wiki/info, they have nice scripts for append/clean/troubleshoot.) <br><br>This started as an over the top experiment for learning some HTML/CSS/JavaScript (first released 2019.01.02, compare added 2020.02.22). <br>This is a viewer/tool, and not an editor/installer. <br><br></div><hr></details> <!-- user.js (and user-overrides.js) --> <br><details><summary>user.js (and user-overrides.js)</summary> <div class="indentdiv"><br> A user.js file can be used by Mozilla Firefox (some forks and Thunderbird) for configuration. It allows you to enforce preference settings (user_pref) on browser start up. These are the settings accessed when you type 'about:config' into the URL address box. (See: <a target="_blank" rel="external noopener noreferrer" href="http://kb.mozillazine.org/User.js_file" >mozillaZine User.js file</a> and <a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js/wiki" >arkenfox user.js wiki</a>) <br><br>The user.js file needs to be placed in your Firefox profile folder, type 'about:profiles' into the URL address box to help find this. Make a backup copy of your Firefox profile folder. <br><br>Various online sites provide details of user_pref settings. Some projects (ie arkenfox user.js) publish their user.js template, with instructions and information/wiki. These projects may update their content regularly, keep your user.js up to date, settings may also change with each Firefox version. <br><br>A user-overrides.js file is not used directly by Firefox, it is a arkenfox user.js idea, please read about this at the wiki link given above. Basically, you take a projects user.js template, and append your user-overrides.js onto it, to form your user.js. The user-overrides.js file contains your personal user_pref settings including any differences you require. (user-template.js + user-overrides.js = user.js) <br><br>There is no user.js content distributed with this HTML file, user.js templates can be obtained from third party sources and are subject to their license. If this HTML file contains any user.js code then it has been added at a later stage by another party or end user, or it has been auto loaded using URL parameter options. <br><br></div><hr></details> <!-- Loading content --> <br><details><summary>Loading content</summary> <div class="indentdiv"><br> Load user.js content into the text boxes by:<br> <div class="indentdiv"> <br><details><summary>PASTE (directly into the text box)</summary> <div class="indentdiv"><br> Paste the content into the text boxes under [Menu]>[Actions]. Open the user.js first in a text editor or in a browser tab to copy it. A link for arkenfox user.js is provided under [Menu]>[Links]. <br><br> The links shown above the text input boxes can also be used to open files in a browser tab for copy/paste. <br><br> Those links are to files in a folder that is either, in the same location as you placed the userjs-tool.html file, or in the location specified by the folder= URL parameter (see Initial states). </div></details> <br><details><summary>[Load] (local file or from URL)</summary> <div class="indentdiv"><br> Use the [Menu]>[Actions]>[Load] buttons to open the browser file selection dialog for local files, or load a file from a URL (input/pick). <br><br> With local files, select a single local .js file to load into the current text box, or select multiple files for loading into Boxes 1/2/3/4 (tries to match file names to the boxes). (You can drag files into the file selector to display that file location.) (You can also use the local links shown above the text input boxes. Right click and "Copy Link Location", then when the file selector is open press Ctrl-V (paste) and the selector will display that file location. Those links are to files in a folder that is either, in the same location as you placed the userjs-tool.html file, or in the location specified by the folder= URL parameter (see Initial states).) <br><br> When loading from a URL, if you use extensions that control fetch/XMLHttpRequest you might have to allow the connection (eg uMatrix XHR). Some sites may not allow file fetch, visit the URL and copy/paste instead. </div></details> <br><details><summary>DRAG (and drop into the text box)</summary> <div class="indentdiv"><br> Single file into the box, or multiple files are matched by name to load into the other boxes. </div></details> <br><details><summary>AUTO (load file from a URL)</summary> <div class="indentdiv"><br> Automatic load of file from a URL, specified with combinations of URL variables (see "Initial states" below, eg ?expand=true&action=view1&load1=URL). <br><br> When loading from a URL, if you use extensions that control fetch/XMLHttpRequest you might have to allow the connection (eg uMatrix XHR). Some sites may not allow file fetch, visit the site and copy/paste instead. <br><br> (Default browser security prevents loading same origin local files (file://...) with this method, and changing the "security.fileuri.strict_origin_policy" preference to false is not recommended as it relaxes more than just this.) </div></details> <br><details><summary>EMBED (not recommended, will need maintaining)</summary> <div class="indentdiv"><br> Edit this HTML file and paste "base64 encoded user.js" data within the <body> section (near the end). It will be decoded and displayed on opening. See the [Menu]>[Actions]>[Encode] option. </div></details> </div> <br></div><hr></details> <!-- Theme --> <br><details><summary>Theme</summary> <div class="indentdiv"><br> This is not syntax highlighting, just simple highlighting. Inactive user_pref (those within comments) have the same highlighting style as active user_pref. <br><br> [Menu]>"View+ style on view" is on by default, this shows in-block comments as in-line comments, if you set this off some inactive user_pref will have '//' preceding them, others within '/* */' comment blocks (which may be multi-line) will have no style indicating they are inactive. <br><br> Some arkenfox user.js sections (such as: RFP ALTERNATIVES, and DEPRECATED/ESR) use comment blocks, a text editor (such as Kate, Notepad++, or Meld diff tool) would grey out everything in those commented sections, this viewer does not. <br><br> Opens using default theme, unless the theme to use is specified with URL variables (see "Initial states" below). If you want to override the default theme with your own colors, or to add more custom color themes, save and edit the optional "userjs-tool-themes.css" file in the same location as userjs-tool.html. <br><br></div><hr></details> <!-- about:config links/[Groups] --> <br><details><summary>about:config links and [Groups] (search/regex/Web Console)</summary> <div class="indentdiv"><br> <!-- links/search --> Due to Firefox security, about:config links will not open when clicked, either:<br> <div class="indentdiv"> <br><details><summary>(Firefox v71+ paste link into about:config's search box)</summary> <div class="indentdiv"><br> The Firefox about:config page changed from Desktop version 71 and "about:config?filter=" style search links are deprecated, they still open the "about:config" page, and you can then paste the unedited link into the about:config search box (because the links here are styled to work). </div></details> <br><details><summary>COPY (into URL address box)</summary> <div class="indentdiv"><br> Right click the link and "Copy link location", "Paste and go" into the URL address box of a new tab. (On Android long press the link to see the copy link option, but if on newer Android Firefox the copy option is not shown on long press you will have to try the PREFIX method shown below) (on Android the link works in the URL box or in the about:config search box).<br> <br></div></details> <div class="indentdiv"> (links FF71+ too: right click, a, hold Ctrl: T, V, Enter, V) </div> <br><details><summary>OPEN (in new tab via right click)</summary> <div class="indentdiv"><br> Right click the link and "Open Link in New Tab", change to tab, click in URL address box and press enter to load. (This may not work in some browser versions). </div></details> <br><details><summary>PREFIX (menu option/about:blank#)</summary> <div class="indentdiv"><br> Use the [Menu]>"Prefix about:config" option to prefix about:config links with "about:blank#", just to make them clickable, then you must remove that prefix in the URL box. If the link only opens about:config without filtering (such as on newer Firefox 71+) then copy and paste the link into the about:config search box </div></details> <br><details><summary>BOOKMARKING (not recommended: they get out dated)</summary> <div class="indentdiv"><br> The links will open from bookmarks, mass import them by: <ul> <li>when viewing the user.js use the browser's "Save Page As..." (Ctrl+S) to save a HTML file (remove it afterwards)</li> <li>go into the browser's "Library", "Bookmarks", "Show All Bookmarks" (Ctrl+Shift+B or Ctrl+Shift+O)</li> <li>consider using bookmarks "Backup..." as a precaution</li> <li>use "Import Bookmarks from HTML..." (may be slow)</li> <li>keep them up to date (remove/replace)</li> </ul> <br></div></details> <div class="indentdiv"> (bookmarks FF71+ too: right click, c, left click, Ctrl+V) </div> <br><details><summary>FUNCTION ("Web Console" regular expressions)</summary> <div class="indentdiv"><br> You can use the regular expressions from the links with JavaScript functions to access preferences, copy/paste/edit or:<br> <ul> <li>Use the [Menu]>"Function about:config link" option to style the about:config links as "about:blank#ujtFindPref({style:3,name:REGEX});"</li> <li>When on the about:config page open Firefox "Web Console" (Ctrl+Shift+K)</li> <li>Paste your function into the Web Console, eg for find (filter/list) user preferences see the [Menu]>[Actions]>[a:c Functions] button for "ujtFindPref".</li> <li>Or input JavaScript to create a call to another function, eg: var ujtFindPref = function(options) { anotherFunction(options["name"]); };</li> <li>Copy/paste the link to use into the Web Console, you must remove the "about:blank#" prefix. </ul> </div></details> </div> <br>The [Groups] button provides links for accessing batches of preferences in about:config (to display multiple different preferences), the groups are based on the user.js sections. Useful when reviewing, troubleshooting, temporary changes, manual tweaks on Android, etc. <br><br>When using about:config some preferences are hidden, and will only show up if they have been created/user set. Also, if you change any settings through about:config which are active in your user.js, they will change back to the user.js value on browser restart. <br><br></div><hr></details> <!-- initial states --> <br><details><summary>Initial states (and URL parameter variable options)</summary> <div class="indentdiv"><br> Some initial states can be set with (one or more) URL parameter variables:<br> <br> <div class="indentdiv"> PATH/userjs-tool.html<b>?</b>option=value<b>&</b>option=value... </div><br> <ul> <li>size=80 (font size percent, default: 100, Android: 150)</li> <li>wrap=true (line wrap)</li> <li>expand=true (Expand all sections on View)</li> <li>viewplus=false (comments shown in-line etc, default: true)</li> <li>groups=true *(display [Groups] upon view)</li> <li>prefix=true (prefixed about:config links: about:blank#)</li> <li>prefix=function (about:config links to function link)</li> <li>theme=default (default|dark|light|mono|inverse|none|...)</li> <li>folder=file:///PATH (location for the local file links (shown above the input boxes), default: same folder as userjs-tool.html)</li> <li>box=a (open to [Actions] box 0(none)|1|2|3|4|a(all)|v(vertical)</li> <li>loadX=URL (auto-load file from URL into box number X)</li> <li>loadX=data:text/plain;base64,bm90IHByaXZhdGU= (load non-private text)</li> <li>action=ACTION show functions|links|help (eg ACTION=functions) <li>action=ACTION (when loadX=URL used), ACTION: <div class="indentdiv"> viewX (view box number 0(none)|1|2|3|4)<br> tableX (table view (arkenfox user.js) of box number 1|2|3|4)<br> groupsX (show [Groups] for box number 1|2|3|4)<br> compare:X:Y (compare box numbers 1|2|3|4 and 1|2|3|4)<br> compare:X:Y:show (show: groupx6|groupx12|multiple|az|unsorted|diffstring)<br> compare:X:Y:show:layout (layout: 5column|3column|2column)<br> append,skeleton,collect,reduce,clean,byvalue,togroup<br> </div> </li> <li>jump=TEXT (expand/focus first section containing TEXT with auto-load)</li> <li>a (auto-load arkenfox user.js)</li> <li>av (auto-load arkenfox user.js and view)</li> <li>at (auto-load arkenfox user.js and table view)</li> </ul> <br> <details><summary>Examples</summary> <ul> <li>https://icpantsparti.github.io/firefox-user.js-tool/userjs-tool.html</li> <li>file:///C:/Users/USERNAME/Downloads/userjs-tool.html</li> <li>file:///home/USERNAME/Downloads/userjs-tool.html</li> <li>?box=a&expand=true</li> <li>?theme=light&size=150</li> <li>?folder=file:///C:/Users/USERNAME/AppData/Roaming/Mozilla/Firefox/Profiles/XXXXXXXX.default-release</li> <li>?folder=file:///home/USERNAME/.mozilla/firefox/XXXXXXXX.default-release</li> <li>?av or ?action=view1&box=a&load1=https://raw.githubusercontent.com/arkenfox/user.js/master/user.js</li> <li>?at or ?action=table1&box=a&load1=https://raw.githubusercontent.com/arkenfox/user.js/master/user.js</li> <li>?action=compare:1:4&box=a&load1=https://raw.githubusercontent.com/arkenfox/user.js/master/user.js&load4=https://raw.githubusercontent.com/arkenfox/user.js/72.0/user.js</li> </ul></details> <br></div><hr></details> <!-- Acknowledgments --> <br><details><summary>Acknowledgments</summary> <ul> <br> <li>Many thanks to <a target="_blank" rel="external noopener noreferrer" href="https://github.com/arkenfox/user.js" >https://github.com/arkenfox/user.js 💎</a> and all particiPANTS. The HTML rendered view of user.js follows a similar style (and dark/light theme) to the static HTML that arkenfox user.js once produced. user-overrides.js append, prefs.js cleaner, //// --- comment-out --- are arkenfox user.js methods.</li> <br> <li>Thanks <a target="_blank" rel="external noopener noreferrer" href="http://www.ghacks.net/" >http://www.ghacks.net/</a> who hosted earlier versions of arkenfox/user.js (named "ghacks user.js" at that time).</li> <br> <li><a target="_blank" rel="external noopener noreferrer" href="https://github.com/rndme/download" >https://github.com/rndme/download</a> for embedded JavaScript library download.js which enables [Save]. (pyllyukko once posted on github about https://github.com/Genbox/HardenIT (dormant?) which used that).</li> <br> <li><a target="_blank" rel="external noopener noreferrer" href="https://github.com/claustromaniac/Compare-UserJS" >https://github.com/claustromaniac/Compare-UserJS 😺</a> which inspired [Compare] (not a port).</li> <br> <li><a target="_blank" rel="external noopener noreferrer" href="https://github.com/overdodactyl" >https://github.com/overdodactyl 🦜</a> made a work in progress prototype (on shinyapps.io) in early 2019 which inspired [Table View] (not a port).</li> <br> <li><a target="_blank" rel="external noopener noreferrer" href="https://johnresig.com/files/jsdiff.js" >https://johnresig.com/files/jsdiff.js</a> for embedded JavaScript library jsdiff.js, diffString used on [Compare]> [DiffStr].</li> <br> <li>This does not depend on JQuery or similar. Other acknowledgments (eg stackoverflow etc) are shown in the code.</li> <br> <li><a target="_blank" rel="external noopener noreferrer" href="https://kate-editor.org/get-it/">Kate (text editor)</a></li> <br> <li><a target="_blank" rel="external noopener noreferrer" href="http://meldmerge.org/">Meld (visual diff/merge)</a></li> <br> </ul> <hr></details> <!-- input box buttons --> <br><details><summary>Input box buttons on [Menu]>[Actions]</summary> <div class="indentdiv"><br> Closing or refreshing the browser tab will lose any content added into the [Action] text boxes.<br> <br> These buttons are on the left of each text input box:<br> <ul> <br> <li>▣ro Indicates the box is readonly, click to toggle.</li> <br> <li>[View] the user.js (that you pasted/loaded into the text box) with some nice HTML formatting. [Menu]>"View+ style on view" is on by default, this shows in-block comments as in-line comments, to make inactive user_pref within /*multi-line commented*/ sections show prefixed with //, and with arkenfox/user.js this also adds some extra line spacing. If you toggle [Menu]>"View+ style on view" it is effective on the next use of a [View] button.</li> <br> <li>[Table View] for viewing the user.js in a table format. Shows the user.js (that you pasted/loaded into a text box) with some nice HTML formatting. Mainly for arkenfox/user.js.</li> <br> <li>[▾Load/Save] Load file(s) into the box(es) (eg user.js, user-overrides.js). Opens the browser file selection dialog. Select a single local .js file to load into the current text box, or multiple file selections for loading into the corresponding text boxes. (Also see Loading content info above)<br><br> Save the box contents as a file (Desktop Firefox allows you to choose the location and file name).</li> <br> <li>[Select] highlight all text in the box.</li> <br> <li>[Clear] the text in the box (and switch readonly off).</li> </ul> <br></div><hr></details> <!-- actions buttons --> <br><details><summary>Actions buttons on [Menu]>[Actions]</summary> <div class="indentdiv"><br> These action buttons are below the text input boxes. The numbers on the buttons indicate the input>output boxes, contents of the output box are usually replaced, [Collect] amends the output box.<br> <!-- Actions - [Compare] --> <br> <details><summary>[▾Compare]</summary> <div class="indentdiv"><br> Display a table highlighting the user_pref differences between two boxes and show a summary, output into the view area. If a user_pref appears twice the most recent active value takes priority, and is shown in the table. You might also want to see this PowerShell alternative: <a target="_blank" rel="external noopener noreferrer" href="https://github.com/claustromaniac/Compare-UserJS" >https://github.com/claustromaniac/Compare-UserJS 😺</a>. <br><br> Display options:<br><br> <ul> <li>[Group x6] (by value/state)</li> <li>[Group x12] (by value/state/active/inactive)</li> <li>[Multiple] (by multiple occurrence/alphabetical)</li> <li>[A-Z] (by alphabetical)</li> <li>[Unsorted] (by order found)</li> <li>[DiffStr] (string difference) <br> (Uses an embedded 3rd party library, see Help/Acknowledgments) <br> (It is better to use file comparison software, eg meld) </li> <li>[Layout] (change table between 5columnx1row 3columnx2row 2columnx2row)</li> </ul> </div></details> <!-- Actions - [Append] --> <br> <details><summary>[Append 1+2>3]</summary> <div class="indentdiv"><br> Take the user-template.js (eg arkenfox/user.js) (from Box 1) and append your user-overrides.js (from Box 2), place the result into Box 3. <br><br> This will also process any lines from Box 2 of the format: <u>//// --- comment-out --- 'prefname.goes.here'</u> as they occur, ie any user_pref with that name in the output so far will be "inactivated", commented out with "////COMMENT-OUT:". Care: if you comment out prefs you may also have to manually reset their value in about:config. <br><br> If your user-overrides.js contains a line that reads: <u>//// --- add-override-comment ---</u> then the tag "////OVERRIDE:" will be placed in front of any overridden user_pref in the user.js output. <br><br> "////COMMENT-OUT:" and "////OVERRIDE:" tags are displayed inverted when using the viewer so they stand out. <br><br> Note: any in-block/multi-line comments (/*...*/) in the user-overrides.js are recognized, user_pref within those comments are inactive, and appear in the appended user-overrides.js part of the output as in-line comments (//). </div></details> <!-- Actions - [Collect] --> <br> <details><summary>[Collect 1>+2]</summary> <div class="indentdiv"><br> Helps to create a user-overrides.js, by allowing point/click/edit collection of user_pref. (Note: this is very basic) <br><br> It first takes you into the usual viewing of Box 1 content, with the added ability to: <br><br> click on the word "user_pref" (to toggle collection of either <u>//// --- comment-out --- 'prefname.goes.here'</u> or of an active user_pref) (note: comment-out is a reminder to prompt you to make sure that pref gets inactivated in your user.js) <br><br> click on the user_pref "value" (to collect the user_pref with a new value, toggles boolean, input box for integer/string). <br><br> This does not change Box 1 content, the 'amendments' are just collected in Box 2. <br><br> The "Actions" panel is displayed with reduced size for viewing of Box 2 changes, press the yellow [End Collect] button when you have finished (shown next to [Overrides] box only during collect, scroll the actions panel or click [<b><</b>Back] if it is out of view). <br><br> Box 2 [Select] button becomes [Select+], and the first instance of the pref name toggled is selected, if you wish to select all click elsewhere in the box and click the select button again. </div></details> <!-- Actions - [Skeleton] --> <br> <details><summary>[Skeleton 1>2]</summary> <div class="indentdiv"><br> Create a skeleton for a user-overrides.js. Reduce the user-template.js (from Box 1) to headings and commented user_pref, place the result into Box 2. </div></details> <!-- Actions - [Reduce] --> <br> <details><summary>[Reduce 1>4]</summary> <div class="indentdiv"><br> Reduce the user-template.js (from Box 1) to headings and user_pref lines only, place the result into Box 4. </div></details> <!-- Actions - [Clean] --> <br> <details><summary>[Clean 4-3>4]</summary> <div class="indentdiv"><br> Remove user_pref from prefs.js in Box 4, based on the active and inactive user_pref from user.js in Box 3. <br><br>Use with care, this might clean more (or even less) than you expect. Do not replace your prefs.js while Firefox is running, and rename the old file as a backup. <br><br>The arkenfox/user.js pref cleaner scripts are a better way to do this, read at the arkenfox/user.js/wiki, note the pros/cons. </div></details> <!-- Actions - [By Value] --> <br> <details><summary>[By Value 3>4]</summary> <div class="indentdiv"><br> Take the user.js from box 3 and arrange the user_pref by values, output into box 4. This shows the user_pref ordered by active/inactive: false, true, 0, 1, 2, other integer, empty string, other string. <br><br> Any re-occurring user_pref in the user.js will only show once with most recent active value priority. <br><br> This is mainly intended as an aid if manually reviewing user.js settings on Android Firefox (although you may be able to inject user.js to Android via PC (about:debugging) see the [Menu]>[Actions]>[a:c Functions] button and the arkenfox/user.js/wiki). <br><br> The idea is that you can use the about:config?filter= links under [Groups] to bulk display user_pref that require a similar value, these links can be long pressed to copy and then pasted in the about:config search. Remember that hidden preferences will only show if they have been user created. </div></details> <!-- Actions - [To Group] --> <br> <details><summary>[To Group 4>4]</summary> <div class="indentdiv"><br> Convert a list of user preferences in Box 4 to an about:config?filter= group, place output into Box 4. If you have a bunch of favorite preferences, place the list into Box 4 and turn them into an about:config link that displays that group of preferences. The list can be newline, space, comma, or other separated. </div></details> <!-- Actions - [Encode] --> <br> <details><summary>[▾Encode 4>4]</summary> <div class="indentdiv"><br> Various encode/decode options (eg Base64, URI), performed on the contents of Box 4 (input/output on Box 4). Encode to multi line Base64 if you are going to save user.js data into the <body> section (near the end) of this HTML file, because you want it to display when this HTML is opened. (Not recommended, as needs constant maintaining, better to just [Load]/paste fresh content). </div></details> <br> <details><summary>[a:c Functions]</summary> <div class="indentdiv"><br> Show JavaScript functions for Mozilla Firefox/Thunderbird about:config, and information on how to use them. To find (filter/list)/reset/set user preferences and values. Use [Menu]>[Actions], or [<b><</b>Back] button to return. </div></details> <!-- Actions - [Links] --> <br> <details><summary>[Links]</summary> <div class="indentdiv"><br> Display some useful links (arkenfox/user.js, profile locations). The button takes you to [Menu]>[Links]. Use [Menu]>[Actions], or [<b><</b>Back] button to return. </div></details> <br></div><hr></details> <br><br><br><br><br></div><!-- end: help_panel --> </DL></DL> <!-- [base64 encoded] Functions for about:config (find (filter/list)/reset/set) https://raw.githubusercontent.com/icpantsparti/firefox-user.js-tool/master/userjs-tool-aboutconfig-functions.js --> <div id="base64_aboutconfig_functions" class="hidden"> Ly8gSmF2YVNjcmlwdCBmdW5jdGlvbnMgZm9yIE1vemlsbGEgRmlyZWZveC9UaHVuZGVyYmlyZCBh Ym91dDpjb25maWcKLy8gdXNlciBwcmVmZXJlbmNlcyBhbmQgdmFsdWVzOiBmaW5kIChmaWx0ZXIv bGlzdC9zYXZlL2RlZmF1bHRzKS9yZXNldC9zZXQKLy8KLy8gTmFtZSAgICAgICAgIDogdXNlcmpz LXRvb2wtYWJvdXRjb25maWctZnVuY3Rpb25zLmpzCi8vIFByb2plY3QgICAgICA6IGh0dHBzOi8v Z2l0aHViLmNvbS9pY3BhbnRzcGFydGkvZmlyZWZveC11c2VyLmpzLXRvb2wKLy8gVmVyc2lvbiAg ICAgIDogMjAyMS4wMy4xMSAoYWxwaGEvZXhwZXJpbWVudGFsKQovLyBGaWxlL1VwZGF0ZSAgOiBo dHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vaWNwYW50c3BhcnRpL2ZpcmVmb3gtdXNl ci5qcy10b29sL21hc3Rlci91c2VyanMtdG9vbC1hYm91dGNvbmZpZy1mdW5jdGlvbnMuanMKLy8g TGljZW5zZSAoTUlUKTogaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2ljcGFudHNw YXJ0aS9maXJlZm94LXVzZXIuanMtdG9vbC9tYXN0ZXIvTElDRU5TRQovLyBEaXNjbGFpbWVyICAg OiBVc2Ugd2l0aCBjYXJlIGF0IHlvdXIgb3duIHJpc2ssIGFuZCB2ZXJpZnkgYW55IHJlc3VsdHMK Ly8KLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQovLyBQYXN0ZSB0aGUgdGV4dCBmcm9tIHRoaXMg ZmlsZSBpbnRvIHRoZSBjb25zb2xlIHRvIGFkZCB0aGUgZnVuY3Rpb25zIHRlbXBvcmFyaWx5Ci8v IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KLy8gRmlyZWZveDogb3BlbiAnYWJvdXQ6Y29uZmlnJyB0 aGVuIFdlYiBDb25zb2xlIChDdHJsK1NoaWZ0K0spCi8vIEFuZHJvaWQvUmVtb3RlOiAnYWJvdXQ6 ZGVidWdnaW5nJyBvbiBQQywgJ2Fib3V0OmNvbmZpZycgb24gdGFyZ2V0IGRldmljZQovLyBUaHVu ZGVyYmlyZDogTWVudT5Ub29scz5EZXZlbG9wZXIgVG9vbHM+RXJyb3IgQ29uc29sZSAoQ3RybCtT aGlmdCtKKQovLyBUaHVuZGVyYmlyZC9PdGhlcjogVG9vbHM+RGV2ZWxvcGVyIFRvb2xzPkNvbnRl bnQgRnJhbWUgRGVidWdnZXI+VGFicz5EZWJ1Zz5Db25zb2xlCi8vIE9yIGxvYWQgc2F2ZWQgZmls ZSBpbiB0aGUgY29uc29sZSB3aXRoOgovLyBqYXZhc2NyaXB0OigoKT0+e3dpdGgoZG9jdW1lbnQp e2xldCBzPWNyZWF0ZUVsZW1lbnQoJ3NjcmlwdCcpO3Muc3JjPSdmaWxlOi8vL1lPVVJQQVRIL3Vz ZXJqcy10b29sLWFib3V0Y29uZmlnLWZ1bmN0aW9ucy5qcyc7aGVhZC5hcHBlbmRDaGlsZChzKX19 KSgpOwovLwovLyAtLS0tLS0tCi8vIFN1bW1hcnkKLy8gLS0tLS0tLQovLyBGdW5jdGlvbjogdWp0 RmluZFByZWYoIHsgc3R5bGU6MCwgbmFtZTovLiovaSwgdmFsdWU6Ly4qL2ksIHR5cGU6L1tiaXNd LywgbG9ja2VkOi9beW5dLywgbW9kaWZpZWQ6L1t5bl0vLCBhc2RlZmF1bHQ6L1t5bl0vLCBub2Rl ZmF1bHQ6L1t5bl0vLCBmaWxlb3V0OmZhbHNlIH0gKTsKLy8gRnVuY3Rpb246IHVqdFJlc2V0UHJl ZihwcmVmKTsKLy8gRnVuY3Rpb246IHVqdFNldFByZWYocHJlZiwgdmFsdWUpOwovLyBBbGlhcyAg IDogdXNlcl9wcmVmID0gdWp0U2V0UHJlZjsgIC8vIHNlZSBub3RlcwovLwovLyAtLS0tLS0tLS0t LS0tLS0tLS0tLQovLyB1anRGaW5kUHJlZiBFeGFtcGxlcwovLyAtLS0tLS0tLS0tLS0tLS0tLS0t LQovLyAgIHVqdEZpbmRQcmVmKCk7ICAgICAgICAgICAgICAgICAgICAgICAgLy8gc2hvdyBzb21l IHVzYWdlIGluZm8KLy8gICB1anRGaW5kUHJlZigyKTsgICAgICAgICAgICAgICAgICAgICAgIC8v IGxpc3QgYWxsICsgc2F2ZSB0byBmaWxlIChzcHJlYWRzaGVldCBzdHlsZSkKLy8gICB1anRGaW5k UHJlZih7c3R5bGU6MyxmaWxlb3V0OnRydWV9KTsgIC8vIGxpc3QgYWxsICsgc2F2ZSB0byBmaWxl ICh1c2VyX3ByZWYrY29tbWVudHMgc3R5bGUpCi8vICAgdWp0RmluZFByZWYoe21vZGlmaWVkOi95 L30pOyAgICAgICAgICAgICAgICAvLyBzaG93IG1vZGlmaWVkCi8vICAgdWp0RmluZFByZWYoe21v ZGlmaWVkOi95Lyxhc2RlZmF1bHQ6L3kvfSk7ICAvLyBzaG93IG1vZGlmaWVkPWRlZmF1bHQKLy8g ICB1anRGaW5kUHJlZih7bm9kZWZhdWx0Oi95L30pOyAgICAgICAgICAgICAgIC8vIHNob3cgVHJh c2ggQ2FuIFByZWZzIGVnIGhpZGRlbi9pbnZhbGlkCi8vICAgdWp0RmluZFByZWYoe2xvY2tlZDov eS99KTsgICAgICAgICAgICAgICAgICAvLyBzaG93IGxvY2tlZAovLyAgIHVqdEZpbmRQcmVmKHt2 YWx1ZTovZ29vZ2xlL2l9KTsgICAgICAgICAgICAgLy8gZmluZCB2YWx1ZQovLyAgIHVqdEZpbmRQ cmVmKHttb2RpZmllZDoveS8sdHlwZTovYi8sdmFsdWU6L15mYWxzZSQvaX0pOyAgLy8gc2hvdyBt b2RpZmllZCBib29sZWFuIGZhbHNlCi8vICAgdWp0RmluZFByZWYoe3N0eWxlOjMsbmFtZTovYWN0 aXZpdHktc3RyZWFtL2l9KTsgICAgICAgICAgICAgICAgICAgICAvLyBwYXJ0IG5hbWUKLy8gICB1 anRGaW5kUHJlZih7c3R5bGU6MyxuYW1lOi9eZXh0ZW5zaW9uc1wucG9ja2V0XC5lbmFibGVkJC9p fSk7ICAgICAgIC8vIHdob2xlIG5hbWUKLy8gICB1anRGaW5kUHJlZih7c3R5bGU6MyxuYW1lOi9e KHdob2xlfHN0YXJ0Lip8LiptaWRkbGUuKnwuKmVuZCkkL2l9KTsgIC8vIG11bHRpIG5hbWUKLy8K Ly8gLS0tLS0tLS0tLS0tLQovLyBHZW5lcmFsIE5vdGVzCi8vIC0tLS0tLS0tLS0tLS0KLy8gICAq IG9uIGNvbnNvbGUgcmlnaHQgY2xpY2sgcmVzdWx0cyAiQ29weSBvYmplY3QiIC8gbGVmdCBjbGlj ayBleHBhbmQvY29udHJhY3QKLy8gICAgIChpZiByZXN1bHRzIGRvIG5vdCBjb3B5IHRvIGNsaXBi b2FyZCB0cnkgIkV4cG9ydCB2aXNpYmxlIG1lc3NhZ2VzIHRvIEZpbGUiLAovLyAgICAgb3IgdWp0 RmluZFByZWYgaGFzIGZpbGVvdXQ6dHJ1ZSB0byBzYXZlIHJlc3VsdHMgdG8gYSBmaWxlKQovLyAg ICogY2xlYXIgY29uc29sZS9jb21tYW5kIGhpc3Rvcnk6ICBjbGVhcigpOyAgY2xlYXJIaXN0b3J5 KCk7Ci8vICAgKiBUaHVuZGVyYmlyZCAoY29uc29sZSkgb3BlbiBhYm91dDpjb25maWc6IHdpbmRv dy5vcGVuRGlhbG9nKCJhYm91dDpjb25maWciKTsKLy8gICAqIGlmIHVzaW5nIHJlbW90ZSBkZWJ1 Z2dpbmcgdG8gYWNjZXNzIGFub3RoZXIgZGV2aWNlIChlZyBBbmRyb2lkKToKLy8gICAgIFBDOiB1 c2UgJ2Fib3V0OmRlYnVnZ2luZycgKG9sZGVyIEZpcmVmb3ggd2FzIFdlYklERS9TY3JhdGNocGFk KQovLyAgICAgVGFyZ2V0OiAnYWJvdXQ6Y29uZmlnJyBvcGVuLCAiUmVtb3RlIGRlYnVnZ2luZyIg ZW5hYmxlZCBpbiBGaXJlZm94IHNldHRpbmdzCi8vICAgICAgICAgICAgICJVU0IgZGVidWdnaW5n IiBlbmFibGVkIGluIEFuZHJvaWQgRGV2ZWxvcGVyIHNldHRpbmdzCi8vICAgICByZTogaHR0cHM6 Ly9naXRodWIuY29tL2Fya2VuZm94L3VzZXIuanMvd2lraS8xLjYtRmlyZWZveC1BbmRyb2lkCi8v ICAgICBhbHNvIHJlIGFya2VuZm94L3VzZXIuanMvd2lraTogdW5ibG9jayBhYm91dDpjb25maWcg aW4gbmV3ZXIgdmVyc2lvbnMgKEZGNzErPyk6Ci8vICAgICB1c2VyX3ByZWYoImdlbmVyYWwuYWJv dXRDb25maWcuZW5hYmxlIiwgdHJ1ZSk7Ci8vICAgKiBmdW5jdGlvbnMgbWlnaHQgYWxzbyB3b3Jr IHRocm91Z2ggQnJvd3Nlci9FcnJvciBDb25zb2xlIChDdHJsK1NoaWZ0K0opCi8vICAgKiBzZXQg J3VzZXJfcHJlZicgYWxpYXMgaWYgeW91IHdhbnQgdG8gcGFzdGUgJ3VzZXIuanMnIGNvZGUgdG8g cnVuIGEgZnVuY3Rpb24KLy8gICAgIGVnIGVudGVyICd1c2VyX3ByZWYgPSB1anRTZXRQcmVmOycg b3IgJ3VzZXJfcHJlZiA9IHVqdFJlc2V0UHJlZjsnCi8vICAgKiBkaXN0cmlidXRpb24vcG9saWNp ZXMuanNvbiBjYW4gY2hhbmdlIHByZWZlcmVuY2UgZGVmYXVsdCB2YWx1ZXMgKGFib3V0OnBvbGlj aWVzKQovLyAgICogaGlkZGVuIHByZWZlcmVuY2VzIG9ubHkgc2hvdyBpZiB0aGV5IGhhdmUgYmVl biBjcmVhdGVkL21vZGlmaWVkIChzYW1lIGFzIGFib3V0OmNvbmZpZykKLy8gICAgIGV4YW1wbGVz OiBodHRwczovL3NlYXJjaGZveC5vcmcvbW96aWxsYS1jZW50cmFsL3NlYXJjaD9xPShleHRlbnNp b25zXC5nZXRBZGRvbnNcLnNob3dQYW5lfHByaXZhY3lcLnJlc2lzdEZpbmdlcnByaW50aW5nXC5s ZXR0ZXJib3hpbmd8dWlcLnByZWZlcnNSZWR1Y2VkTW90aW9uKSZwYXRoPSZjYXNlPWZhbHNlJnJl Z2V4cD10cnVlCi8vCi8vIC0tLS0tLS0tLS0tLS0tLS0tCi8vIHVqdEZpbmRQcmVmIE5vdGVzCi8v IC0tLS0tLS0tLS0tLS0tLS0tCi8vICAgKiB1anRGaW5kUHJlZiggeyBzdHlsZTowLCBuYW1lOi8u Ki9pLCB2YWx1ZTovLiovaSwgdHlwZTovW2Jpc10vLCBsb2NrZWQ6L1t5bl0vLCBtb2RpZmllZDov W3luXS8sIGFzZGVmYXVsdDovW3luXS8sIG5vZGVmYXVsdDovW3luXS8sIGZpbGVvdXQ6ZmFsc2Ug fSApOwovLyAgICogc3R5bGU6Ci8vICAgICAgIDA9cmVnZXggICAgIChkZWZhdWx0KSBwYXN0ZSBy ZXN1bHQgaW50byB0aGUgYWJvdXQ6Y29uZmlnIHNlYXJjaCBib3gKLy8gICAgICAgMT1ib29rbWFy ayAgJ2Fib3V0OmNvbmZpZz9maWx0ZXI9JyAoRkY3MSsgY29weSwgY2xpY2ssIHBhc3RlIGluIHNl YXJjaCkKLy8gICAgICAgMj1saXN0K2RlZmF1bHQgICAgICAgIHBhc3RlIGludG8gYSBzcHJlYWRz aGVldCBmb3Igc29ydC9maWx0ZXIvc2VhcmNoCi8vICAgICAgIDM9dXNlcl9wcmVmK2NvbW1lbnRz ICB1c2VyX3ByZWYoInByZWYiLCB2YWx1ZSk7ICAvLyBsb2NrZWQgIC8vIG1vZGlmaWVkICAvLyBu b2RlZmF1bHQKLy8gICAgICAgND11c2VyX3ByZWYgICAgICAgICAgIHVzZXJfcHJlZigicHJlZiIs IHZhbHVlKTsKLy8gICAgICAgNT11c2VyX3ByZWZAZGVmYXVsdCAgIHVzZXJfcHJlZigicHJlZiIs IHZhbHVlRGVmYXVsdCk7Ci8vICAgKiB0eXBlOiBiPWJvb2xlYW4gaT1pbnRlZ2VyIHM9c3RyaW5n Ci8vICAgKiBsb2NrZWQvbW9kaWZpZWQvYXNkZWZhdWx0L25vZGVmYXVsdDogeT15ZXMgbj1ubwov LyAgICogbW9kaWZpZWQ6L3kvIC0gdXNlciBzZXQgcHJlZmVyZW5jZXMgKGVnIHVzZXIuanMvYWJv dXQ6Y29uZmlnL2V0YykKLy8gICAgIHttb2RpZmllZDoveS8sYXNkZWZhdWx0Oi95L30gLSBmaW5k cyB0aG9zZSB1c2VyIHNldCB0byBzYW1lIGFzIGRlZmF1bHQKLy8gICAqIGFzZGVmYXVsdDoveS8g LSBpZiBwcmVmZXJlbmNlIHZhbHVlIGlzIHRoZSBzYW1lIGFzIHRoZSBkZWZhdWx0IHZhbHVlCi8v ICAgKiBub2RlZmF1bHQ6L3kvIC0gcHJlZnMgd2l0aCBhIHRyYXNoIGNhbiBpY29uIG9uIGFib3V0 OmNvbmZpZyBlZyBoaWRkZW4vaW52YWxpZAovLyAgICogZmlsZW91dDp0cnVlIC0gc2F2ZSByZXN1 bHRzIHRvIGRlc2t0b3AgdXNlcmpzLXRvb2wtYWJvdXRjb25maWctWVlZWU1NREQtSEhNTVNTLVNT Uy50eHQKLy8gICAqIHJldHVybiBhbmQgbmV3bGluZSBjaGFyYWN0ZXJzIGluIHByZWZlcmVuY2Ug dmFsdWVzIGFyZSByZXBsYWNlZCBhcyBcciBcbgovLyAgICogbm93IHVzaW5nIFNlcnZpY2VzLnBy ZWZzLmdldENvbXBsZXhWYWx1ZSB0byBsb29rIHVwIHRoZSB2YWx1ZSBvZgovLyAgICAgcHJlZmVy ZW5jZXMgd2hpY2ggYXJlIGluaXRpYWxseSByZWFkIGFzICJjaHJvbWU6Li4uIgovLwovLyAtLS0t LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQovLyB1anRTZXRQcmVmL3VqdFJlc2V0UHJlZiBOb3Rl cwovLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQovLyAgICogdGhlc2Ugd2VyZSBtYWlu bHkgaW50ZW5kZWQgZm9yIHdoZW4gaW5qZWN0aW5nIHByZWZlcmVuY2VzIHRvIEFuZHJvaWQKLy8g ICAqIG9uIGRlc2t0b3AgeW91IHdvdWxkIHVzdWFsbHkgdXNlIHVzZXIuanMvcHJvZmlsZXMgZm9y IHByZWZlcmVuY2UgY29udHJvbAovLyAgICAgKGFsdGhvdWdoIHVqdFJlc2V0UHJlZiBjb3VsZCBi ZSB1c2VmdWwgZm9yIGJhdGNoIGNsZWFyaW5nIHByZWZzKQovLyAgICogaW5jb3JyZWN0IHVzYWdl IHJpc2tzIG1lc3NpbmcgdXAgeW91ciBGaXJlZm94IHNldHRpbmdzIChpZiB5b3UgYXJlIHVzaW5n Ci8vICAgICB0aGlzIG9uIGRlc2t0b3A6IGJhY2t1cCB5b3VyIEZpcmVmb3ggcHJvZmlsZSwgb3Ig J3ByZWZzLmpzJyBhdCBsZWFzdCwKLy8gICAgIGFuZCByZW1lbWJlciB0aGF0IHVzZXIuanMgY2Fu IG92ZXJyaWRlIGNoYW5nZXMgb24gRmlyZWZveCByZXN0YXJ0KQovLyAgICogdWp0U2V0UHJlZiBv dXRwdXRzICdjcmVhdGluZycgdG8gdGhlIGNvbnNvbGUgd2hlbiB0aGUgcHJlZmVyZW5jZSBkaWQK Ly8gICAgIG5vdCBhbHJlYWR5IGV4aXN0ICh0aGVzZSBhcmUgZWl0aGVyIGhpZGRlbiBwcmVmZXJl bmNlcywgb3IgaW52YWxpZCkKLy8KLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLS0tLS0tLQovLyBBY2tub3dsZWRnbWVudHMgKHRoaXMgaXMgYmFzZWQgb24gaW5m by9jb2RlIGZyb20pCi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0KLy8gICBodHRwczovL2dpdGh1Yi5jb20vYXJrZW5mb3gvdXNlci5qcwovLyAgICAg aHR0cHM6Ly9naXRodWIuY29tL2Fya2VuZm94L3VzZXIuanMvd2lraS8xLjYtRmlyZWZveC1BbmRy b2lkCi8vICAgICBodHRwczovL2dpdGh1Yi5jb20vYXJrZW5mb3gvdXNlci5qcy90cmVlL21hc3Rl ci9zY3JhdGNocGFkLXNjcmlwdHMKLy8gICAgIGh0dHBzOi8vZ2l0aHViLmNvbS9hcmtlbmZveC91 c2VyLmpzL2Jsb2IvbWFzdGVyL3NjcmF0Y2hwYWQtc2NyaXB0cy9hcmtlbmZveC1jbGVhci1yZW1v dmVkLmpzCi8vICAgICBodHRwczovL2dpdGh1Yi5jb20vYXJrZW5mb3gvdXNlci5qcy9ibG9iL21h c3Rlci9zY3JhdGNocGFkLXNjcmlwdHMvdHJvdWJsZXNob290ZXIuanMKLy8gICBodHRwczovL3N0 YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zNzk2MDg0L2Fib3V0LWNvbmZpZy1wcmVmZXJlbmNl cy1hbmQtanMKLy8gICBodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL01v emlsbGEvSmF2YVNjcmlwdF9jb2RlX21vZHVsZXMvU2VydmljZXMuanNtCi8vICAgaHR0cDovL3d3 dy5vcGVuanMuY29tL2FydGljbGVzL29wdGlvbmFsX2Z1bmN0aW9uX2FyZ3VtZW50cy5waHAKLy8g ICBodHRwczovL2R4ci5tb3ppbGxhLm9yZy9tb3ppbGxhLWNlbnRyYWwvc291cmNlL21vZHVsZXMv bGlicHJlZi9uc0lQcmVmQnJhbmNoLmlkbCMzMQovLyAgIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemls bGEub3JnL2VuLVVTL2RvY3MvQXJjaGl2ZS9BZGQtb25zL0NvZGVfc25pcHBldHMvRmlsZV9JX08K Ly8gICBodHRwczovL2dpdGh1Yi5jb20vVGhlZW1pbS9HZWNrb1ByZWZzRXhwb3J0ZXIgICAoZmls ZSBvdXQgbWV0aG9kKQoKaWYgKHR5cGVvZihTZXJ2aWNlcykgPT0gInVuZGVmaW5lZCIpIHsKICBj b25zb2xlLmxvZygiLy8gcmVxdWlyZXMgU2VydmljZXMgKGVnIGFib3V0OmNvbmZpZyBhcyBhY3Rp dmUgdGFiKSIpOwp9CgovLyBvbGRlciB2ZXJzaW9ucyBvZiBGaXJlZm94L2ZvcmtzL1RodW5kZXJi aXJkL1NlYU1vbmtleSB1c2UgLi4uQ2hhclByZWYKdHJ5IHsKICB2YXIgdWp0VXNlQ2hhclByZWYg PSAhKHR5cGVvZihTZXJ2aWNlcy5wcmVmcy5nZXRTdHJpbmdQcmVmKSA9PSAiZnVuY3Rpb24iKTsK fQpjYXRjaChlKSB7IH0KCnZhciB1anRGaW5kUHJlZiA9IGZ1bmN0aW9uKG9wdGlvbnMpIHsKICB2 YXIgcmVzdWx0ID0gIiI7CiAgaWYgKHR5cGVvZiBvcHRpb25zID09ICJ1bmRlZmluZWQiKSB7CiAg ICBvcHRpb25zID0geyAnZmlsZW91dCc6IGZhbHNlIH07CiAgICAvLyBpZiBubyBvcHRpb25zIHNw ZWNpZmllZCBzaG93IHNvbWUgdXNhZ2UgaW5mbwogICAgcmVzdWx0ID0gIi8vIHVzYWdlOlxuIgog ICAgICArICcvLyB1anRGaW5kUHJlZiggeyBzdHlsZTowLCBuYW1lOi8uKi9pLCB2YWx1ZTovLiov aSwgdHlwZTovW2Jpc10vLCBsb2NrZWQ6L1t5bl0vLCBtb2RpZmllZDovW3luXS8sIGFzZGVmYXVs dDovW3luXS8sIG5vZGVmYXVsdDovW3luXS8sIGZpbGVvdXQ6ZmFsc2UgfSApO1xuJwogICAgICAr ICcvLyBzdHlsZTogMD1yZWdleCAxPWJvb2ttYXJrIDI9bGlzdCtkZWZhdWx0IDM9dXNlcl9wcmVm K2NvbW1lbnRzIDQ9dXNlcl9wcmVmIDU9dXNlcl9wcmVmQGRlZmF1bHRcbicKICAgICAgKyAnLy8g Yj1ib29sZWFuIGk9aW50ZWdlciBzPXN0cmluZyB5PXllcyBuPW5vXG4nCiAgICAgICsgJy8vIGVn IHVqdEZpbmRQcmVmKDIpOyB1anRGaW5kUHJlZih7c3R5bGU6MyxmaWxlb3V0OnRydWV9KTsgdWp0 RmluZFByZWYoe21vZGlmaWVkOi95L30pOyB1anRGaW5kUHJlZih7bm9kZWZhdWx0Oi95L30pO1xu JzsKICB9CiAgZWxzZSB7CiAgICAvLyBmdW5jdGlvbiB0byBnZXQgdGhlIGRlZmF1bHQgdmFsdWUg b2YgYSBwcmVmZXJlbmNlCiAgICB2YXIgdWp0R2V0UHJlZkRlZmF1bHQgPSBmdW5jdGlvbihwcmVm TmFtZSkgewogICAgICB2YXIgZGVmYXVsdFZhbHVlOwogICAgICB0cnkgewogICAgICAgIHN3aXRj aCAoU2VydmljZXMucHJlZnMuZ2V0RGVmYXVsdEJyYW5jaCgiIikuZ2V0UHJlZlR5cGUocHJlZk5h bWUpKSB7CiAgICAgICAgICBjYXNlIDEyODoKICAgICAgICAgICAgZGVmYXVsdFZhbHVlID0gU2Vy dmljZXMucHJlZnMuZ2V0RGVmYXVsdEJyYW5jaCgiIikuZ2V0Qm9vbFByZWYocHJlZk5hbWUpLnRv U3RyaW5nKCk7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgY2FzZSA2NDoKICAgICAgICAg ICAgZGVmYXVsdFZhbHVlID0gU2VydmljZXMucHJlZnMuZ2V0RGVmYXVsdEJyYW5jaCgiIikuZ2V0 SW50UHJlZihwcmVmTmFtZSkudG9TdHJpbmcoKTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAg ICBjYXNlIDMyOgogICAgICAgICAgICBkZWZhdWx0VmFsdWUgPSB1anRVc2VDaGFyUHJlZiA/IFNl cnZpY2VzLnByZWZzLmdldERlZmF1bHRCcmFuY2goIiIpLmdldENoYXJQcmVmKHByZWZOYW1lKQog ICAgICAgICAgICAgIDogU2VydmljZXMucHJlZnMuZ2V0RGVmYXVsdEJyYW5jaCgiIikuZ2V0U3Ry aW5nUHJlZihwcmVmTmFtZSk7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgZGVmYXVsdDoK ICAgICAgICAgICAgcmV0dXJuIG51bGw7CiAgICAgICAgfQogICAgICB9CiAgICAgIGNhdGNoKGUp IHsKICAgICAgICByZXR1cm4gbnVsbDsKICAgICAgfQogICAgICByZXR1cm4gZGVmYXVsdFZhbHVl OwogICAgfQogICAgLy8gcHJlcGFyZSBmb3Igb3B0aW9ucyBiZWluZyBhIHNpbmdsZSByZWdleC9z dHJpbmcvbnVtYmVyCiAgICB2YXIgbm9BcmdzTWF0Y2hlZCA9IHRydWUsIG5hbWVPbmx5ID0gb3B0 aW9ucywgc3R5bGVPbmx5ID0gMywgZmlsZU91dEV4ID0gZmFsc2U7CiAgICBpZiAodHlwZW9mIG9w dGlvbnMgPT0gInN0cmluZyIpIHsKICAgICAgbmFtZU9ubHkgPSBuZXcgUmVnRXhwKG9wdGlvbnMs ICJpIik7CiAgICAgIGZpbGVPdXRFeCA9IHRydWU7CiAgICAgIG9wdGlvbnMgPSB7fTsKICAgIH0K ICAgIGVsc2UgaWYgKHR5cGVvZiBvcHRpb25zID09ICJudW1iZXIiKSB7CiAgICAgIG5hbWVPbmx5 ID0gbmV3IFJlZ0V4cCgiLioiLCAiaSIpOwogICAgICBzdHlsZU9ubHkgPSBvcHRpb25zOwogICAg ICBmaWxlT3V0RXggPSB0cnVlOwogICAgICBvcHRpb25zID0ge307CiAgICB9CiAgICAvLyBlbmZv cmNlIGRlZmF1bHRzIGZvciB1bnNwZWNpZmllZCBvcHRpb25zCiAgICB2YXIgZGVmYXVsdEFyZ3Mg PSB7ICdzdHlsZSc6IDAsICduYW1lJzogLy4qLywgJ3ZhbHVlJzogLy4qLywgJ3R5cGUnOiAvLiov LCAnbG9ja2VkJzogLy4qLywKICAgICAgJ21vZGlmaWVkJzogLy4qLywgJ2FzZGVmYXVsdCc6IC8u Ki8sICdub2RlZmF1bHQnOiAvLiovLCAnZmlsZW91dCc6IGZhbHNlIH0KICAgIGZvcih2YXIgaSBp biBkZWZhdWx0QXJncykgewogICAgICBpZiAodHlwZW9mIG9wdGlvbnNbaV0gPT0gInVuZGVmaW5l ZCIpIHsKICAgICAgICBvcHRpb25zW2ldID0gZGVmYXVsdEFyZ3NbaV07CiAgICAgIH0KICAgICAg ZWxzZSBpZiAodHlwZW9mIG9wdGlvbnNbaV0gPT0gInN0cmluZyIpIHsKICAgICAgICBvcHRpb25z W2ldID0gbmV3IFJlZ0V4cChvcHRpb25zW2ldLCAiaSIpOwogICAgICAgIG5vQXJnc01hdGNoZWQg PSBmYWxzZTsKICAgICAgfQogICAgICBlbHNlIHsKICAgICAgICBub0FyZ3NNYXRjaGVkID0gZmFs c2U7CiAgICAgIH0KICAgIH0KICAgIC8vIHdoZW4gb3B0aW9ucyB3YXMgYSBzaW5nbGUgcmVnZXgv c3RyaW5nL251bWJlcgogICAgaWYgKG5vQXJnc01hdGNoZWQpIHsKICAgICAgb3B0aW9uc1sibmFt ZSJdID0gbmFtZU9ubHk7CiAgICAgIG9wdGlvbnNbInN0eWxlIl0gPSBzdHlsZU9ubHk7CiAgICAg IG9wdGlvbnNbImZpbGVvdXQiXSA9IGZpbGVPdXRFeDsKICAgIH0KICAgIC8vIGJlZ2luIGZvcm1p bmcgcmVzdWx0CiAgICBzd2l0Y2ggKG9wdGlvbnNbInN0eWxlIl0pIHsKICAgICAgY2FzZSA1Ogog ICAgICAgIHJlc3VsdCA9ICIvLyBkZWZhdWx0IHZhbHVlc1xuIjsKICAgICAgICBicmVhazsKICAg ICAgY2FzZSA0OgogICAgICBjYXNlIDM6CiAgICAgICAgYnJlYWs7CiAgICAgIGNhc2UgMjoKICAg ICAgICByZXN1bHQgPSAiW1ByZWZlcmVuY2VOYW1lXVx0W1R5cGVdXHRbVmFsdWVdXHRbTG9ja2Vk XVx0W01vZGlmaWVkXVx0W1NhbWVBc0RlZmF1bHRdXHRbRGVmYXVsdFZhbHVlXVx0W05vRGVmYXVs dF1cbiI7CiAgICAgICAgYnJlYWs7CiAgICAgIGNhc2UgMToKICAgICAgICByZXN1bHQgPSAnYWJv dXQ6Y29uZmlnP2ZpbHRlcj0nOwogICAgICBkZWZhdWx0OgogICAgICAgIHJlc3VsdCArPSAnL15c XCokfF4oJzsKICAgIH0KICAgIC8vIGxvb3AgdGhyb3VnaCBhbGwgcHJlZnMKICAgIHZhciB0eXBl LCB2YWx1ZSwgbG9ja2VkLCBtb2RpZmllZCwgYXNEZWZhdWx0LCBub0RlZmF1bHQsIHZhbHVlRGVm YXVsdDsKICAgIC8vIHNhbWUgcHJlZnM6IFNlcnZpY2VzLnByZWZzLmdldERlZmF1bHRCcmFuY2go IiIpLmdldENoaWxkTGlzdCgiIikuc29ydCgpCiAgICAvLyAgICAgICAgICAgICBTZXJ2aWNlcy5w cmVmcy5nZXRCcmFuY2goIiIpLmdldENoaWxkTGlzdCgiIikuc29ydCgpCiAgICAvLyAgICAgICAg ICAgICBTZXJ2aWNlcy5wcmVmcy5nZXRDaGlsZExpc3QoIiIpLnNvcnQoKQogICAgZm9yIChjb25z dCBwcmVmTmFtZSBvZiBTZXJ2aWNlcy5wcmVmcy5nZXRDaGlsZExpc3QoIiIpLnNvcnQoKSkgewog ICAgICAvLyBnZXQgcHJlZiBpbmZvCiAgICAgIG1vZGlmaWVkID0gKFNlcnZpY2VzLnByZWZzLnBy ZWZIYXNVc2VyVmFsdWUocHJlZk5hbWUpID8gJ3knIDogJ24nKTsKICAgICAgbG9ja2VkID0gKFNl cnZpY2VzLnByZWZzLnByZWZJc0xvY2tlZChwcmVmTmFtZSkgPyAneScgOiAnbicpOwogICAgICBz d2l0Y2ggKFNlcnZpY2VzLnByZWZzLmdldFByZWZUeXBlKHByZWZOYW1lKSkgewogICAgICAgIGNh c2UgMTI4OgogICAgICAgICAgdHlwZSA9ICJiIjsKICAgICAgICAgIHZhbHVlID0gU2VydmljZXMu cHJlZnMuZ2V0Qm9vbFByZWYocHJlZk5hbWUpLnRvU3RyaW5nKCk7CiAgICAgICAgICB2YWx1ZURl ZmF1bHQgPSB1anRHZXRQcmVmRGVmYXVsdChwcmVmTmFtZSk7CiAgICAgICAgICBicmVhazsKICAg ICAgICBjYXNlIDY0OgogICAgICAgICAgdHlwZSA9ICJpIjsKICAgICAgICAgIHZhbHVlID0gU2Vy dmljZXMucHJlZnMuZ2V0SW50UHJlZihwcmVmTmFtZSkudG9TdHJpbmcoKTsKICAgICAgICAgIHZh bHVlRGVmYXVsdCA9IHVqdEdldFByZWZEZWZhdWx0KHByZWZOYW1lKTsKICAgICAgICAgIGJyZWFr OwogICAgICAgIGNhc2UgMzI6CiAgICAgICAgICB0eXBlID0gInMiOwogICAgICAgICAgdmFsdWUg PSB1anRVc2VDaGFyUHJlZiA/IFNlcnZpY2VzLnByZWZzLmdldENoYXJQcmVmKHByZWZOYW1lKQog ICAgICAgICAgICA6IFNlcnZpY2VzLnByZWZzLmdldFN0cmluZ1ByZWYocHJlZk5hbWUpOwogICAg ICAgICAgdmFsdWVEZWZhdWx0ID0gdWp0R2V0UHJlZkRlZmF1bHQocHJlZk5hbWUpOwogICAgICAg ICAgLy8gZ2V0IHRoZSBhY3R1YWwgdmFsdWUgd2hlbiAiY2hyb21lOi4uLiIKICAgICAgICAgIHRy eSB7CiAgICAgICAgICAgIGlmIChtb2RpZmllZCA9PSAnbicgJiYgL15jaHJvbWU6XC9cLy4rXC9s b2NhbGVcLy4rXC5wcm9wZXJ0aWVzLy50ZXN0KHZhbHVlKSkgewogICAgICAgICAgICAgIHZhbHVl ID0gU2VydmljZXMucHJlZnMuZ2V0Q29tcGxleFZhbHVlKHByZWZOYW1lLCBDaS5uc0lQcmVmTG9j YWxpemVkU3RyaW5nKS5kYXRhOwogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgICBj YXRjaChlKSB7IHZhbHVlID0gIiI7IH0KICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgIGlmICgv XmNocm9tZTpcL1wvLitcL2xvY2FsZVwvLitcLnByb3BlcnRpZXMvLnRlc3QodmFsdWVEZWZhdWx0 KSkgewogICAgICAgICAgICAgIHZhbHVlRGVmYXVsdCA9IFNlcnZpY2VzLnByZWZzLmdldENvbXBs ZXhWYWx1ZShwcmVmTmFtZSwgQ2kubnNJUHJlZkxvY2FsaXplZFN0cmluZykuZGF0YTsKICAgICAg ICAgICAgfQogICAgICAgICAgfQogICAgICAgICAgY2F0Y2goZSkgeyB2YWx1ZURlZmF1bHQgPSAi IjsgfQogICAgICAgICAgYnJlYWs7CiAgICAgICAgZGVmYXVsdDoKICAgICAgICAgIHZhbHVlID0g bnVsbDsgdHlwZSA9IG51bGw7IGxvY2tlZCA9IG51bGw7IG1vZGlmaWVkID0gbnVsbDsKICAgICAg ICAgIGFzRGVmYXVsdCA9IG51bGw7IG5vRGVmYXVsdCA9IG51bGw7IHZhbHVlRGVmYXVsdCA9IG51 bGw7CiAgICAgIH0KICAgICAgaWYgKHZhbHVlRGVmYXVsdCA9PT0gbnVsbCkgeyBub0RlZmF1bHQg PSAieSI7IH0gZWxzZSB7IG5vRGVmYXVsdCA9ICJuIjsgfTsKICAgICAgaWYgKHZhbHVlID09PSB2 YWx1ZURlZmF1bHQpIHsgYXNEZWZhdWx0ID0gInkiOyB9IGVsc2UgeyBhc0RlZmF1bHQgPSAibiI7 IH07CiAgICAgIC8vIGFkZCB0byByZXN1bHQgaWYgbWF0Y2hlcyBvcHRpb25zCiAgICAgIGlmICgg KG9wdGlvbnNbIm5hbWUiXS50ZXN0KHByZWZOYW1lKSkKICAgICAgICAmJiAob3B0aW9uc1sidmFs dWUiXS50ZXN0KHZhbHVlKSkKICAgICAgICAmJiAob3B0aW9uc1sidHlwZSJdLnRlc3QodHlwZSkp CiAgICAgICAgJiYgKG9wdGlvbnNbImxvY2tlZCJdLnRlc3QobG9ja2VkKSkKICAgICAgICAmJiAo b3B0aW9uc1sibW9kaWZpZWQiXS50ZXN0KG1vZGlmaWVkKSkKICAgICAgICAmJiAob3B0aW9uc1si YXNkZWZhdWx0Il0udGVzdChhc0RlZmF1bHQpKQogICAgICAgICYmIChvcHRpb25zWyJub2RlZmF1 bHQiXS50ZXN0KG5vRGVmYXVsdCkpCiAgICAgICAgICkKICAgICAgewogICAgICAgIC8vIHF1b3Rl L2VzY2FwZSBzdHJpbmcKICAgICAgICBpZiAoKHR5cGUgPT0gInMiKSAmJiAob3B0aW9uc1sic3R5 bGUiXSA9PSA1CiAgICAgICAgICB8fCBvcHRpb25zWyJzdHlsZSJdID09IDQgfHwgb3B0aW9uc1si c3R5bGUiXSA9PSAzKSkKICAgICAgICB7CiAgICAgICAgICB2YWx1ZSA9ICciJyArIHZhbHVlLnJl cGxhY2UoLyhbIlxcXSkvZywgJ1xcJDEnKSArICciJzsKICAgICAgICAgIGlmICh2YWx1ZURlZmF1 bHQgIT09IG51bGwpIHsKICAgICAgICAgICAgdmFsdWVEZWZhdWx0ID0gJyInICsgdmFsdWVEZWZh dWx0LnJlcGxhY2UoLyhbIlxcXSkvZywgJ1xcJDEnKSArICciJwogICAgICAgICAgfQogICAgICAg IH0KICAgICAgICBpZiAodmFsdWVEZWZhdWx0ID09PSBudWxsKSB7IHZhbHVlRGVmYXVsdCA9ICJu dWxsIiB9CiAgICAgICAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKC9cbi9nLCAiXFxuIikucmVwbGFj ZSgvXHIvZywgIlxcciIpOwogICAgICAgIHZhbHVlRGVmYXVsdCA9IHZhbHVlRGVmYXVsdC5yZXBs YWNlKC9cbi9nLCAiXFxuIikucmVwbGFjZSgvXHIvZywgIlxcciIpOwogICAgICAgIHN3aXRjaCAo b3B0aW9uc1sic3R5bGUiXSkgewogICAgICAgICAgY2FzZSA1OiAvLyA1PXVzZXJfcHJlZkBkZWZh dWx0CiAgICAgICAgICAgIHJlc3VsdCArPSAndXNlcl9wcmVmKCInICsgcHJlZk5hbWUgKyAnIiwg JyArIHZhbHVlRGVmYXVsdCArICIpO1xuIjsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICBj YXNlIDQ6IC8vIDQ9dXNlcl9wcmVmCiAgICAgICAgICAgIHJlc3VsdCArPSAndXNlcl9wcmVmKCIn ICsgcHJlZk5hbWUgKyAnIiwgJyArIHZhbHVlICsgIik7XG4iOwogICAgICAgICAgICBicmVhazsK ICAgICAgICAgIGNhc2UgMzogLy8gMz11c2VyX3ByZWYrY29tbWVudHMKICAgICAgICAgICAgcmVz dWx0ICs9ICd1c2VyX3ByZWYoIicgKyBwcmVmTmFtZSArICciLCAnICsgdmFsdWUgKyAiKTsiCiAg ICAgICAgICAgICAgKyBsb2NrZWQucmVwbGFjZSgvbi9nLCAiIikucmVwbGFjZSgveS9nLCAiICAv LyBsb2NrZWQiKQogICAgICAgICAgICAgICsgbW9kaWZpZWQucmVwbGFjZSgvbi9nLCAiIikucmVw bGFjZSgveS9nLCAiICAvLyBtb2RpZmllZCIpOwogICAgICAgICAgICBpZiAoYXNEZWZhdWx0ID09 ICJ5IiAmJiBtb2RpZmllZCA9PSAieSIpIHsgcmVzdWx0ICs9ICI9ZGVmYXVsdCIgfQogICAgICAg ICAgICByZXN1bHQgKz0gbm9EZWZhdWx0LnJlcGxhY2UoL24vZywgIiIpLnJlcGxhY2UoL3kvZywg IiAgLy8gbm9kZWZhdWx0IikgKyAiXG4iOwogICAgICAgICAgICBicmVhazsKICAgICAgICAgIGNh c2UgMjogLy8gMj1saXN0K2RlZmF1bHQKICAgICAgICAgICAgcmVzdWx0ICs9IHByZWZOYW1lICsg Ilx0IiArIHR5cGUgKyAiXHQiICsgdmFsdWUgKyAiXHQiICsgbG9ja2VkICsgIlx0IgogICAgICAg ICAgICAgICsgbW9kaWZpZWQgKyAiXHQiICsgYXNEZWZhdWx0ICsgIlx0IiArIHZhbHVlRGVmYXVs dCArICJcdCIgKyBub0RlZmF1bHQgKyAiXG4iOwogICAgICAgICAgICBicmVhazsKICAgICAgICAg IGRlZmF1bHQ6IC8vIDA9cmVnZXggLyAxPWJvb2ttYXJrCiAgICAgICAgICAgIHJlc3VsdCArPSBw cmVmTmFtZS5yZXBsYWNlKC8oWyouK10pL2csICJcXCQxIikgKyAifCI7CiAgICAgICAgfQogICAg ICB9CiAgICB9IC8vIChlbmQgb2YgbG9vcCB0aHJvdWdoIGFsbCBwcmVmcykKICAgIC8vIGZpbmlz aCBmb3JtaW5nIHJlc3VsdAogICAgc3dpdGNoIChvcHRpb25zWyJzdHlsZSJdKSB7CiAgICAgIGNh c2UgNToKICAgICAgY2FzZSA0OgogICAgICBjYXNlIDM6CiAgICAgIGNhc2UgMjogYnJlYWs7CiAg ICAgIGRlZmF1bHQ6CiAgICAgICAgcmVzdWx0ID0gcmVzdWx0LnJlcGxhY2UoL1x8JC8sICcnKSAr ICcpKDt8JCl8XiQvaSc7CiAgICAgICAgLy8gdXBkYXRlIGFib3V0OmNvbmZpZyBkaXNwbGF5IChp Z25vcmUgZXJyb3JzIGZvciB0aHVuZGVyYmlyZC9ldGMpCiAgICAgICAgdHJ5IHsKICAgICAgICAg IC8vIHB1dCByZXN1bHQgaW50byB0aGUgYWJvdXQ6Y29uZmlnIHNlYXJjaCBib3ggYW5kIGZpbHRl cgogICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImFib3V0LWNvbmZpZy1zZWFyY2gi KS52YWx1ZSA9IHJlc3VsdDsKICAgICAgICAgIGZpbHRlclByZWZzKHsgc2hvcnRTdHJpbmc6IHRy dWUgfSk7CiAgICAgICAgICAvLyB0ZW1wb3JhcmlseSBjaGFuZ2UgYWJvdXQ6Y29uZmlnIHRvIGNv bXBhY3Qgc2l6ZQogICAgICAgICAgZG9jdW1lbnQuYm9keS5zdHlsZS5mb250U2l6ZSA9ICcwLjk4 ZW0nOwogICAgICAgICAgZm9yICh2YXIgcyBvZiBbICcjcHJlZnMnLCAnI3ByZWZzIConLCAnI3By ZWZzID4gdHInLCAnI3ByZWZzID4gdHIgPiB0ZCcsCiAgICAgICAgICAgICcjcHJlZnMgPiB0ciA+ IHRoJywgJyNwcmVmcyBidXR0b24nLCAnLmFkZCcgXSkKICAgICAgICAgIHsKICAgICAgICAgICAg dmFyIG8gPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKHMpOwogICAgICAgICAgICBmb3IgKHZh ciBpID0gMCwgaiA9IG8ubGVuZ3RoOyBpIDwgajsgaSsrKSB7CiAgICAgICAgICAgICAgaWYgKHMg PT0gJy5hZGQnKSB7CiAgICAgICAgICAgICAgICAvLyBoaWRlIHRoZSBhZGQgKGxhc3QgbGluZSBv bmx5IHNob3dpbmcgdGhlIHNlYXJjaCBjcml0ZXJpYSkKICAgICAgICAgICAgICAgIG9baV0uc3R5 bGUuZGlzcGxheSA9ICdub25lJzsKICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgZWxzZSB7 CiAgICAgICAgICAgICAgICBvW2ldLnN0eWxlLm1pbkhlaWdodCA9ICIxLjJlbSI7CiAgICAgICAg ICAgICAgICBvW2ldLnN0eWxlLmhlaWdodCA9ICJ1bnNldCI7CiAgICAgICAgICAgICAgfQogICAg ICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGNhdGNoKGUpIHsgfQogICAg fQogIH0KICBjb25zb2xlLmxvZyhyZXN1bHQpOwogIGlmIChvcHRpb25zWyJmaWxlb3V0Il0pIHsK ICAgIC8vIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvQXJjaGl2ZS9B ZGQtb25zL0NvZGVfc25pcHBldHMvRmlsZV9JX08KICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS9U aGVlbWltL0dlY2tvUHJlZnNFeHBvcnRlcgogICAgdHJ5IHsKICAgICAgLy8gIkN1cldvcmtEIiBt aWdodCBiZSByZWFkb25seSBwcm9ncmFtIGZvbGRlciBzbyB1c2UgIkRlc2siCiAgICAgIHZhciBm aWxlID0gU2VydmljZXMuZGlyc3ZjLmdldCgiRGVzayIsIENvbXBvbmVudHMuaW50ZXJmYWNlcy5u c0lGaWxlKTsKICAgICAgdmFyIGQgPSBuZXcgRGF0ZSgpOwogICAgICBmaWxlLmFwcGVuZCgidXNl cmpzLXRvb2wtYWJvdXRjb25maWctIiArIGQuZ2V0RnVsbFllYXIoKQogICAgICAgICsgKChkLmdl dE1vbnRoKCkrMSkgPCAxMCA/ICIwIiA6ICIiKSArIChkLmdldE1vbnRoKCkrMSkKICAgICAgICAr IChkLmdldERhdGUoKSA8IDEwID8gIjAiIDogIiIpICsgZC5nZXREYXRlKCkKICAgICAgICArICIt IiArIChkLmdldEhvdXJzKCkgPCAxMCA/ICIwIiA6ICIiKSArIGQuZ2V0SG91cnMoKQogICAgICAg ICsgKGQuZ2V0TWludXRlcygpIDwgMTAgPyAiMCIgOiAiIikgKyBkLmdldE1pbnV0ZXMoKQogICAg ICAgICsgKGQuZ2V0U2Vjb25kcygpIDwgMTAgPyAiMCIgOiAiIikgKyBkLmdldFNlY29uZHMoKQog ICAgICAgICsgIi0iICsgZC5nZXRNaWxsaXNlY29uZHMoKSArICIudHh0IgogICAgICApOwogICAg ICBjb25zb2xlLmxvZygnLy8gc2F2aW5nIHJlc3VsdCB0byBmaWxlOiAnICsgZmlsZS5wYXRoKTsK ICAgICAgdmFyIGZvU3RyZWFtID0gQ29tcG9uZW50cy5jbGFzc2VzWyJAbW96aWxsYS5vcmcvbmV0 d29yay9maWxlLW91dHB1dC1zdHJlYW07MSJdCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgLmNyZWF0ZUluc3RhbmNlKENvbXBvbmVudHMuaW50ZXJmYWNlcy5uc0lGaWxlT3V0cHV0U3Ry ZWFtKTsKICAgICAgZm9TdHJlYW0uaW5pdChmaWxlLCAweDAyIHwgMHgwOCB8IDB4MjAsIHBhcnNl SW50KCIwNjY2IiwgOCksIDApOwogICAgICB2YXIgY29udmVydGVyID0gQ29tcG9uZW50cy5jbGFz c2VzWyJAbW96aWxsYS5vcmcvaW50bC9jb252ZXJ0ZXItb3V0cHV0LXN0cmVhbTsxIl0KICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAuY3JlYXRlSW5zdGFuY2UoQ29tcG9uZW50cy5pbnRl cmZhY2VzLm5zSUNvbnZlcnRlck91dHB1dFN0cmVhbSk7CiAgICAgIGNvbnZlcnRlci5pbml0KGZv U3RyZWFtLCAiVVRGLTgiLCAwLCAwKTsKICAgICAgY29udmVydGVyLndyaXRlU3RyaW5nKHJlc3Vs dCk7CiAgICAgIGNvbnZlcnRlci5jbG9zZSgpOwogICAgfQogICAgY2F0Y2goZSkgewogICAgICBj b25zb2xlLmxvZygiLy8gKHVqdEZpbmRQcmVmKSBmaWxlIHNhdmUgZXJyb3I6XG4iK2UrIlxuIik7 CiAgICB9CiAgfQp9Cgp2YXIgdWp0U2V0UHJlZiA9IGZ1bmN0aW9uKHByZWYsIHZhbHVlKSB7CiAg dHJ5IHsKICAgIGlmIChTZXJ2aWNlcy5wcmVmcy5nZXRQcmVmVHlwZShwcmVmKSA9PSAwKSB7CiAg ICAgIGNvbnNvbGUubG9nKCIvLyAodWp0U2V0UHJlZikgY3JlYXRpbmc6ICIgKyBwcmVmICsgIlxu Iik7CiAgICB9CiAgICBzd2l0Y2ggKHR5cGVvZiB2YWx1ZSkgewogICAgICBjYXNlICJib29sZWFu IjoKICAgICAgICBTZXJ2aWNlcy5wcmVmcy5zZXRCb29sUHJlZihwcmVmLCB2YWx1ZSk7CiAgICAg ICAgYnJlYWs7CiAgICAgIGNhc2UgIm51bWJlciI6CiAgICAgICAgU2VydmljZXMucHJlZnMuc2V0 SW50UHJlZihwcmVmLCB2YWx1ZSk7CiAgICAgICAgYnJlYWs7CiAgICAgIGNhc2UgInN0cmluZyI6 CiAgICAgICAgaWYgKHVqdFVzZUNoYXJQcmVmKSB7IFNlcnZpY2VzLnByZWZzLnNldENoYXJQcmVm KHByZWYsIHZhbHVlKSB9CiAgICAgICAgZWxzZSB7IFNlcnZpY2VzLnByZWZzLnNldFN0cmluZ1By ZWYocHJlZiwgdmFsdWUpIH0KICAgICAgICBicmVhazsKICAgIH0KICB9CiAgY2F0Y2goZSkgewog ICAgY29uc29sZS5sb2coIi8vICh1anRTZXRQcmVmKSBlcnJvcjogIiArIGUgKyAiIHByZWY6ICIK ICAgICAgKyBwcmVmICsgIiB2YWx1ZTogIiArIHZhbHVlICsgIlxuIik7CiAgfQp9Cgp2YXIgdWp0 UmVzZXRQcmVmID0gZnVuY3Rpb24ocHJlZikgewogIHRyeSB7CiAgICBpZiAoU2VydmljZXMucHJl ZnMucHJlZkhhc1VzZXJWYWx1ZShwcmVmKSkgewogICAgICBTZXJ2aWNlcy5wcmVmcy5jbGVhclVz ZXJQcmVmKHByZWYpOwogICAgICBpZiAoU2VydmljZXMucHJlZnMucHJlZkhhc1VzZXJWYWx1ZShw cmVmKSkgewogICAgICAgIGNvbnNvbGUubG9nKCIvLyAodWp0UmVzZXRQcmVmKSBmYWlsZWQ6ICIg KyBwcmVmICsgIlxuIik7CiAgICAgIH0KICAgIH0KICB9CiAgY2F0Y2goZSkgewogICAgY29uc29s ZS5sb2coIi8vICh1anRSZXNldFByZWYpIGVycm9yOiAiICsgZSArICIgcHJlZjogIiArIHByZWYg KyAiXG4iKTsKICB9Cn0KCi8vIHNldCB1c2VyX3ByZWYgYWxpYXMgdG8gZnVuY3Rpb24gcmVxdWly ZWQgKHRoZW4geW91IGNhbiBwYXN0ZS9ydW4gdXNlci5qcykKdmFyIHVzZXJfcHJlZiA9IHVqdFNl dFByZWY7Ci8vIHZhciB1c2VyX3ByZWYgPSB1anRSZXNldFByZWY7CgovKiBlbmQgb2YgdXNlcmpz LXRvb2wtYWJvdXRjb25maWctZnVuY3Rpb25zLmpzICovCg== </div> <!-- the above base64 encoded block is functions for about:config --> <!-- ************************************* If you paste "base64 encoded user.js" within a <div> below it will be decoded, loaded, and displayed on opening. (box 2 does not auto display) Use the "Menu>Actions>[Encode]" option to create the base64 data. example: <div id="base64_box_4">LyogdGVzdCAqLw==</div> (It is better to use the ui, as content pasted here will soon go stale) ************************************* --> <div id="base64_box_1" class="hidden"></div> <div id="base64_box_2" class="hidden"></div> <div id="base64_box_3" class="hidden"></div> <div id="base64_box_4" class="hidden"></div> </body> </html>