<!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, "&amp;");
    // n = n.replace(/</g, "&lt;");
    // n = n.replace(/>/g, "&gt;");
    // n = n.replace(/"/g, "&quot;");

    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 = { '&amp;': '&', '&lt;': '<', '&gt;': '>',
          '&quot;': '"', '&#039;': "'" };
        return text.replace(/&amp;|&lt;|&gt;|&quot;|&#039;/g,
          function(m) { return map[m]; });
      }
      else if (action == "encode+quotes") {
        // encode (+ quotes)
        var map = { '&': '&amp;', '<': '&lt;', '>': '&gt;',
          '"': '&quot;', "'": '&#039;' };
        return text.replace(/[&<>"']/g, function(m) { return map[m]; });
      }
      else if (action == "decode") {
        // decode
        var map = { '&amp;': '&', '&lt;': '<', '&gt;': '>' };
        return text.replace(/&amp;|&lt;|&gt;/g,
          function(m) { return map[m]; });
      }
      else {
        // encode
        var map = { '&': '&amp;', '<': '&lt;', '>': '&gt;' };
        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>&#x25BE;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">&#9650;</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">&#9660;</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>&nbsp;&nbsp;(Uses a 3rd party library, see Help/Acknowledgments)"
            + "<br>&nbsp;&nbsp;(It is better to use file comparison software, eg meld)"
            + '<br><br>(<ins>1st</ins>: ' + input_box_short_name + ')'
            + '&nbsp;&nbsp;(<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>&#x25BE;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(/&lt;del&gt;/g, "<del>")
            .replace(/&lt;\/del&gt;/g, "</del>")
            .replace(/&lt;ins&gt;/g, "<ins>")
            .replace(/&lt;\/ins&gt;/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 &amp; multiple occurrence" },
          'total1s': { 'id': -2, 'sub': true,  'count': 0, 'name': "In 1st &amp; 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 &amp; multiple occurrence" },
          'total2s': { 'id': -2, 'sub': true,  'count': 0, 'name': "In 2nd &amp; single occurrence" },
          'match+': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:match &amp; State:match" },
          'match+a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match &amp; State:match - active" },
          'match+i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match &amp; State:match - inactive" },
          'match-': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:match &amp; State:differ" },
          'match-a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match &amp; State:differ - active in 1st" },
          'match-i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:match &amp; State:differ - inactive in 1st" },
          'differ+': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:differ &amp; State:match" },
          'differ+a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ &amp; State:match - active" },
          'differ+i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ &amp; State:match - inactive" },
          'differ-': { 'id': -1, 'sub': false, 'count': 0, 'name': "Value:differ &amp; State:differ" },
          'differ-a': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ &amp; State:differ - active in 1st" },
          'differ-i': { 'id': 0, 'sub': true, 'count': 0, 'name': "Value:differ &amp; 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'
          + '&nbsp;&nbsp;(1st: ' + input_box_short_name + ')'
          + '&nbsp;&nbsp;(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>&nbsp;'
      }
      if (/^[ \t\\\*]*date[ \t:]*(.*)[ \t]*$/m.test(input_box.value)) {
        content_html += "("
          + (/^[ \t\\\*]*date[ \t:]*(.*)[ \t]*$/m.exec(input_box.value)[1])
          + ")&nbsp;";
      }
      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>&#x25BE;&#x00A0;&#x25A2;&#x00A0;Filter</option>'
        + '  <option value="SHOWALL">Show all (clear filter)'
        + '&#x00A0;&#x00A0;&#x00A0;&#x00A0;(' + stats["total"].count + ')</option>'
        + '  <option value="HIDEHEADINGS">&#x25A2;&#x00A0;&#x00A0;Hide headings</option>'
        + '  <option value="HIDEINACTIVE">&#x25A2;&#x00A0;&#x00A0;Hide inactive</option>'
        + '  <option value="HIDEACTIVE">&#x25A2;&#x00A0;&#x00A0;Hide active</option>'
        + '  <option value="INVERT">&#x25A2;&#x00A0;&#x00A0;Invert (show the opposite)</option>'
        // + '  <option disabled></option>'
        + '  <option value="TAGS_Active">&#x25A2;&#x00A0;&#x00A0;'
        + 'Active'
        + '&#x00A0;&#x00A0;&#x00A0;&#x00A0;(' + stats["totala"].count + ')</option>'
        + '  <option value="TAGS_Inactive">&#x25A2;&#x00A0;&#x00A0;'
        + 'Inactive'
        + '&#x00A0;&#x00A0;&#x00A0;&#x00A0;(' + 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, "__")
            + '">&#x25A2;&#x00A0;&#x00A0;';
          if (tags[i].indicator) {
            content_html += tags[i].indicator
              + '&#x00A0;&#x00A0;-&#x00A0;&#x00A0;';
          }
          content_html += i + '&#x00A0;&#x00A0;&#x00A0;&#x00A0;('
            + 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">&#9650;</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">&#9660;</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>&#x25BE;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">&#x25A2;&#x00A0;&#x00A0;'
          + '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>&#x25BE;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>&#x25BE;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:
        &#x25EB; WHITE SQUARE WITH VERTICAL BISECTING LINE  (not a toggle)
        &#x25A2; WHITE SQUARE WITH ROUNDED CORNERS  (off/false)
        &#x25A3; WHITE SQUARE CONTAINING BLACK SMALL SQUARE  (on/true)
        &#x00A0; (blank/space) as leading/multi-spaces are lost
        &hellip; symbol for '...'  (not used, looks too small)
        &#x205D; TRICOLON  (vertical ...) (not used)
-->
<select id="menu_select" class="controls borders top_buttons"
title="Select menu option">
  <option value="" disabled selected hidden>&#x25BE;Menu</option>
  <option>&#x25EB;&#x00A0;&#x00A0;Collapse all</option>
  <option>&#x25EB;&#x00A0;&#x00A0;Expand all</option>
  <option>&#x25EB;&#x00A0;&#x00A0;Collapse current/headings</option>
  <option value="" disabled></option>
  <option>&#x25A2;&#x00A0;&#x00A0;[Actions] (input panel)</option>
  <option>&#x25A2;&#x00A0;&#x00A0;[Groups] (about:config search)</option>
  <option>&#x25A2;&#x00A0;&#x00A0;[Links] (template/profiles/etc)</option>
  <option>&#x25A2;&#x00A0;&#x00A0;[about:config Functions]</option>
  <option>&#x25A2;&#x00A0;&#x00A0;[Help/Info]</option>
  <option value="" disabled></option>
  <option>&#x25A2;&#x00A0;&#x00A0;Wrap text</option>
  <option>&#x25A2;&#x00A0;&#x00A0;Prefix about:config links</option>
  <option>&#x25A2;&#x00A0;&#x00A0;Function about:config links</option>
  <option value="" disabled></option>
  <option>&#x25A2;&#x00A0;&#x00A0;Expand all on view</option>
  <option>&#x25A2;&#x00A0;&#x00A0;Show Groups on view</option>
  <option>&#x25A2;&#x00A0;&#x00A0;View+ style on view</option>
  <option value="" disabled></option>
  <option>&#x25A2;&#x00A0;&#x00A0;Theme: default</option>
  <option>&#x25A2;&#x00A0;&#x00A0;Theme: dark</option>
  <option>&#x25A2;&#x00A0;&#x00A0;Theme: light</option>
  <option>&#x25EB;&#x00A0;&#x00A0;Other themes</option>
</select>

<button type="button" id="back_button" class="controls borders top_buttons"
title="Close panel" onclick="backButton();"><b>&lt;</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]&gt;[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">&times;</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><!--
-->&nbsp;&nbsp;<div id="box_1_template_ro">&#x25A2;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>&#x25BE;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="&#10; Add user.js template here (Use [Load] button, drag/drop or paste)&#10;&#10; 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><!--
-->&nbsp;&nbsp;<span id="box_2_overrides_ro">&#x25A2;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>&#x25BE;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="&#10; Add user-overrides.js here (Use [Load] button, drag/drop or paste)&#10;&#10; (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><!--
-->&nbsp;&nbsp;<span id="box_3_userjs_ro">&#x25A2;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>&#x25BE;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="&#10; Add user.js here (Use [Load] button, drag/drop or paste)&#10;&#10; (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><!--
-->&nbsp;&nbsp;<span id="box_4_other_ro">&#x25A2;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>&#x25BE;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="&#10; Add other/user.js here (Use [Load] button, drag/drop or paste)&#10;&#10; (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 &gt;1</button><!--

--><button type="button" class="controls borders" id="load_arkenfox_button"
title="Load current arkenfox user.js into Box 1">Load arkenfox &gt;1</button><!--

--><select class="controls borders" id="compare_select"
title="Compare the user_pref of 2 boxes">
<option value="" disabled selected hidden>&#x25BE;Compare</option>
<option>Compare box 1 &amp; 1 &#x00A0;&#x00A0;A-Z</option>
<option>Compare box 1 &amp; 2 &#x00A0;&#x00A0;&#x25E6;</option>
<option>Compare box 1 &amp; 3 &#x00A0;&#x00A0;&#x25E6;</option>
<option>Compare box 1 &amp; 4 &#x00A0;&#x00A0;&#x25E6;</option>
<option>Compare box 2 &amp; 1</option>
<option>Compare box 2 &amp; 2 &#x00A0;&#x00A0;A-Z</option>
<option>Compare box 2 &amp; 3 &#x00A0;&#x00A0;&#x25E6;</option>
<option>Compare box 2 &amp; 4 &#x00A0;&#x00A0;&#x25E6;</option>
<option>Compare box 3 &amp; 1</option>
<option>Compare box 3 &amp; 2</option>
<option>Compare box 3 &amp; 3 &#x00A0;&#x00A0;A-Z</option>
<option>Compare box 3 &amp; 4 &#x00A0;&#x00A0;&#x25E6;</option>
<option>Compare box 4 &amp; 1</option>
<option>Compare box 4 &amp; 2</option>
<option>Compare box 4 &amp; 3</option>
<option>Compare box 4 &amp; 4 &#x00A0;&#x00A0;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&gt;3</button><!--

--><button type="button" class="controls borders" id="skeleton_button"
title="Create a user-overrides.js skeleton"
>Skeleton 1&gt;2</button><!--

--><button type="button" class="controls borders" id="collect_button"
title="Point and click overrides collector"
>Collect 1&gt;+2</button><!--

--><button type="button" class="controls borders" id="reduce_button"
title="Reduce user.js to just user_pref lines"
>Reduce 1&gt;4</button><!--

--><button type="button" class="controls borders" id="clean_button"
title="Remove user.js user_pref from another .js"
>Clean 4-3&gt;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&gt;4</button><!--

--><button type="button" class="controls borders" id="togroup_button"
title="Preference list to about:config?filter= group"
>To Group 4&gt;4</button><!--

--><select class="controls borders" id="encode_select"
title="Encode/Decode text (Base64, URI)">
<option value="" disabled selected hidden>&#x25BE;Encode 4&gt;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>&lt;</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">&times;</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]&gt;[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">&times;</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. &nbsp;(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]&gt;[Help/Info]&gt;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">&times;</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">&times;</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&nbsp;&nbsp;&nbsp;: 2021.03.11 (alpha/experimental)<br>

Project&nbsp;&nbsp;&nbsp;:
<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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:
<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&nbsp;&nbsp;&nbsp;:
<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&nbsp;&nbsp;&nbsp;:
<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.&nbsp;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 &#128142;</a> inspired, see "Acknowledgments" section below.
&nbsp;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. &nbsp;It allows you to enforce preference settings
(user_pref) on browser start up. &nbsp;These are the settings
accessed when you type 'about:config' into the URL address box.
&nbsp;(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. &nbsp;Make a backup copy of your Firefox
profile folder.

<br><br>Various online sites provide details of user_pref settings.
&nbsp;Some projects (ie arkenfox user.js) publish their user.js
template, with instructions and information/wiki.
&nbsp;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. &nbsp;Basically, you take a projects user.js
template, and append your user-overrides.js onto it, to form
your user.js. &nbsp;The user-overrides.js file contains your
personal user_pref settings including any differences you
require. &nbsp;(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. &nbsp;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]&gt;[Actions].
&nbsp;Open the user.js first in a text editor or in a browser tab to copy it.
&nbsp;A link for arkenfox user.js is provided under [Menu]&gt;[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]&gt;[Actions]&gt;[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). &nbsp;(You can drag files into the
file selector to display that file location.) &nbsp;(You can also use the local
links shown above the text input boxes.&nbsp;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. &nbsp;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). &nbsp;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&amp;action=view1&amp;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). &nbsp;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 &lt;body&gt; section (near the end). &nbsp;It will be
decoded and displayed on opening. &nbsp;See the
[Menu]&gt;[Actions]&gt;[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.
&nbsp;Inactive user_pref (those within comments)
have the same highlighting style as active user_pref.
<br><br>
[Menu]&gt;"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). &nbsp;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. &nbsp;(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]&gt;"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. &nbsp;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]&gt;"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]&gt;[Actions]&gt;[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.
&nbsp;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.
&nbsp;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>&amp;</b>option=value...
</div><br>
<ul>
<li>size=80&nbsp;&nbsp;(font size percent, default: 100, Android: 150)</li>
<li>wrap=true&nbsp;&nbsp;(line wrap)</li>
<li>expand=true&nbsp;&nbsp;(Expand all sections on View)</li>
<li>viewplus=false&nbsp;&nbsp;(comments shown in-line etc, default: true)</li>
<li>groups=true&nbsp;&nbsp;*(display [Groups] upon view)</li>
<li>prefix=true&nbsp;&nbsp;(prefixed about:config links: about:blank#)</li>
<li>prefix=function&nbsp;&nbsp;(about:config links to function link)</li>
<li>theme=default&nbsp;&nbsp;(default|dark|light|mono|inverse|none|...)</li>
<li>folder=file:///PATH&nbsp;&nbsp;(location for the local file links (shown
above the input boxes), default: same folder as userjs-tool.html)</li>
<li>box=a&nbsp;&nbsp;(open to [Actions] box 0(none)|1|2|3|4|a(all)|v(vertical)</li>
<li>loadX=URL&nbsp;&nbsp;(auto-load file from URL into box number X)</li>
<li>loadX=data:text/plain;base64,bm90IHByaXZhdGU=&nbsp;&nbsp;(load non-private text)</li>
<li>action=ACTION&nbsp;&nbsp;show functions|links|help (eg ACTION=functions)
<li>action=ACTION&nbsp;&nbsp;(when loadX=URL used), ACTION:
<div class="indentdiv">
viewX&nbsp;&nbsp;(view box number 0(none)|1|2|3|4)<br>
tableX&nbsp;&nbsp;(table view (arkenfox user.js) of box number 1|2|3|4)<br>
groupsX&nbsp;&nbsp;(show [Groups] for box number 1|2|3|4)<br>
compare:X:Y&nbsp;&nbsp;(compare box numbers 1|2|3|4 and 1|2|3|4)<br>
compare:X:Y:show&nbsp;&nbsp;(show: groupx6|groupx12|multiple|az|unsorted|diffstring)<br>
compare:X:Y:show:layout&nbsp;&nbsp;(layout: 5column|3column|2column)<br>
append,skeleton,collect,reduce,clean,byvalue,togroup<br>
</div>
</li>
<li>jump=TEXT&nbsp;&nbsp;(expand/focus first section containing TEXT with auto-load)</li>
<li>a&nbsp;&nbsp;(auto-load arkenfox user.js)</li>
<li>av&nbsp;&nbsp;(auto-load arkenfox user.js and view)</li>
<li>at&nbsp;&nbsp;(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&amp;expand=true</li>
<li>?theme=light&amp;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&amp;box=a&amp;load1=https://raw.githubusercontent.com/arkenfox/user.js/master/user.js</li>
<li>?at or ?action=table1&amp;box=a&amp;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 &#128142;</a> and all particiPANTS.
&nbsp;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].
&nbsp;(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 &#128570;</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 &#129436;</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]&gt; [DiffStr].</li>
<br>
<li>This does not depend on JQuery or similar. &nbsp;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]&gt;[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>&#x25A3;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. &nbsp;[Menu]&gt;"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.&nbsp; If you
toggle [Menu]&gt;"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.&nbsp;
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>[&#x25BE;Load/Save] Load file(s) into the box(es) (eg user.js,
user-overrides.js). &nbsp;Opens the browser file selection dialog.
&nbsp;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]&gt;[Actions]</summary>
<div class="indentdiv"><br>

These action buttons are below the text input boxes. &nbsp;The numbers on
the buttons indicate the input&gt;output boxes, contents of the output box
are usually replaced, [Collect] amends the output box.<br>

<!-- Actions - [Compare] -->

<br>
<details><summary>[&#x25BE;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. &nbsp;If a user_pref appears
twice the most recent active value takes priority, and is shown in the table.
&nbsp;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 &#128570;</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>&nbsp;&nbsp;(Uses an embedded 3rd party library, see Help/Acknowledgments)
    <br>&nbsp;&nbsp;(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&gt;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:". &nbsp;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&gt;+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>&lt;</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&gt;2]</summary>
<div class="indentdiv"><br>
Create a skeleton for a user-overrides.js. &nbsp;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&gt;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&gt;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.&nbsp; 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&gt;4]</summary>
<div class="indentdiv"><br>
Take the user.js from box 3 and arrange the user_pref by values, output into box
4. &nbsp;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]&gt;[Actions]&gt;[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. &nbsp;
Remember that hidden preferences will only show if they have been user created.
</div></details>

<!-- Actions - [To Group] -->

<br>
<details><summary>[To Group 4&gt;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. &nbsp;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. &nbsp;The list can be newline, space,
comma, or other separated.
</div></details>

<!-- Actions - [Encode] -->

<br>
<details><summary>[&#x25BE;Encode 4&gt;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).
&nbsp;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. &nbsp;To find (filter/list)/reset/set
user preferences and values.
&nbsp;Use [Menu]&gt;[Actions], or [<b>&lt;</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). &nbsp;The
button takes you to [Menu]&gt;[Links].
&nbsp;Use [Menu]&gt;[Actions], or [<b>&lt;</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>