🏠 Home 

WebEraser

Erase parts of any webpage --annoyances, logos, ads, images, etc., permanently with just, Ctrl + Left-Click.


Install this script?
// ==UserScript==
// @name        WebEraser
// @version     1.4.8
// @namespace   sfswe
// @description Erase parts of any webpage --annoyances, logos, ads, images, etc., permanently with just, Ctrl + Left-Click.
// @license     GPL-3.0-only
// @copyright   2018, slow! (https://openuserjs.org/users/slow!)
// @include     *
// @require     https://code.jquery.com/jquery-3.2.1.js
// @require     https://code.jquery.com/ui/1.12.1/jquery-ui.js
// @require https://greasyfork.org/scripts/375359-gm4-polyfill-1-0-1/code/gm4-polyfill-101.js
// @require https://greasyfork.org/scripts/375360-sfs-utils-0-1-5/code/sfs-utils-015.js
// @resource    whiteCurtains      https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtainsDbl.jpg
// @resource    whiteCurtainsOrig  https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains.orig.jpg
// @resource    whiteCurtainsXsm   https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtainsExSm.jpg
// @resource    whiteCurtainsTrpl  https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtainsTrpl.jpg
// @icon        https://raw.githubusercontent.com/SloaneFox/imgstore/master/WebEraserIcon.gif
// @run-at      document-start
// @author      Sloane Fox
// @grant       GM.registerMenuCommand
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM.deleteValue
// @grant       GM.listValues
// @grant       GM.addStyle
// @grant       GM.getResourceText
// @grant       GM.getResourceUrl
// @grant       GM_registerMenuCommand
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_listValues
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getResourceURL
// ==/UserScript==
//
// History
// updated Nov  2018  v1.4.8 Images and Canvas elements lost functionality, returned.
// updated Oct  2018  v1.4.6 Bug fix re complete deletion of element.  Got around webpage trick of translateZ(0) & css hiding.  Block page change mid-setup.
// updated Jan  2018  v1.3.9   Update for yet to come GM4 and already past backward compatibile GM4 polyfill.  And for Chromium.
// updated Nov  2017  v1.3.8   Added extra GM menu command to enable use of Ctrl-e to manually invoke web erasure on a webpage.
// updated Nov  2017  v1.3.7   Added extra GM menu command in case user accidentally erases entire webpage and is faced a blank with not even a WebEraser menu to allow one to undo accident.
// updated Aug  2017  v1.3.6   Issue with iframe when injected code not called for it due to its late creation same origin.
// updated Jan  2017  v1.3.5   Compatibility working on msedge/safari/opera under tampermonkey.  With ie11 (adGuard method) js engine is too old.  Windows ok with Chrome either native or with tamper.
//                             Safari on windows cant run userscripts.
// updated Jan  2017   v1.3.4  Bug fixes.  Issue with load sequence on Chrome.  Monitoring class changes relating to identifier of element.  Compatibility on msedge/safari/opera under tampermonkey.
// updated Dec  2016   v1.3.0  Bug fixes, check for duplicate selectors, color & other ui issues.  Removed zoomer (it used up a little cpu).
// updated Nov  2016.  v1.2.3  Iframe handling for deep iframes.
// updated Oct  2016.  v1.2.2  Fixed bug, GM menu on Chrome not closing.
//                     v1.2.1  Adapted for use also in Google Chrome/Chromium web browser.
// updated Sept 2016.  v1.2    Added user option to turn on the monitoring for new nodes (node mutations).
ttimer("start");
// Globals:
var environ=this, jq_saved, chromert=this.chrome; //window; //this // note 11'17 polyfill acts upon this (sandbox with a member "window") not on window.
if (typeof jQuery!="undefined") jq_saved=jQuery;
var iframe=window!=window.parent, border_width=6;
var win=window,
host=window.document.location.host,
pathname=window.document.location.pathname, webpage=host+pathname, website=host;
var askedAlready,last_one_deleted, delcnt=0, gelem, gelems, gpre_elem,
bblinker, promptOpen, rbcl="sfswe-redborder", pbcl="sfswe-prevborder", tbcl="sfswe-transparentborder";
var tab="&emsp;&emsp;&emsp; &emsp; "; // tab=5spaces, emsp=4spaces, but HTML tab in a <pre> wider hence extra emsp's.
//
// Globs to be initialized asynchronously, see below, init_globs().
//
var page_erasedElems,site_erasedElems,curtain_icon, elems_to_be_hid,curtain_slim_icon,curtain_xslim_icon,
curtain_wide_icon, config, ownImageAddr, whitecurtains, whitecurtainsoriginal, whitecurtainstriple;
var ignoreIdsDupped, curtain_cnt=0;
var zaplists,overlay=false;
if (iframe) {
installEventHandlers();
return;
}
//if (!environInit()) if (!plat_msedge) $(main.bind(environ)); // In a normal GM environment, main will be called at docready.
var str=GM_registerMenuCommand.toString();
//for (var i=0;i<str.length;i++) console.log(str[i]);
if (!environInit(environ)) if (!plat_msedge) {
if(/^complete/.test(document.readyState)) main();
else document.addEventListener("DOMContentLoaded",main.bind(environ)); //main(); addEventListener("load",main.bind(environ))	; // In a normal GM environment, main will be called at docready.
}
Number.prototype.in=function(){for (i of Array.from(arguments)) if (this==i) return true;}; // Use brackets with a literal, eg, (2).in(3,4,2);
Number.prototype.inRange=function(min,max){ if (this >=min && this<=max) return true;}; // Ditto.
Number.prototype.withinRangeOf=function(range,target){ return this.inRange(target-range,target+range); }; // Ditto.
String.prototype.prefix=function(pfix) { return this.length ? pfix+this : ""+this; };
async function main() {try{
log=x=>null;  //logger on/off
ttimer("start of main, state: "+document.readyState);
log("w/e main GM:",GM, "readyState",document.readyState,"body:",document.body,"iframe",iframe,"jQuery:",window.jQuery&&window.jQuery.fn.jquery,"$",window.$);
if (!this.chrome) this.chrome=chromert;
await init_globs();
installEventHandlers();
ensure_jquery_extended(); // may get clobbered by other script loading jQ.
inner_eraseElements("init");
var nerased=$(".Web-Eraser-ed").length, delay=5000+300*(2+nerased), forErasure=getHidElemsCmd("count");
setTimeout(x=> {
//ttimer("start of delay phase ");
log("End of",delay,"delay, checking for inner_eraseElements",page_erasedElems,"or",site_erasedElems);
if(page_erasedElems || site_erasedElems) inner_eraseElements("delay");
else if ($(".Web-Eraser-ed").length==0 && elems_to_be_hid)
console.info("WebEraser message: no match for any selectors:",getHidElemsCmd(),"\nWebpage:",webpage);
var nerased2=$(".Web-Eraser-ed").length;
nerased=nerased2;
installEventHandlers("phase2");
regcmds();
//ttimer("end delay phase ",document.readyState);
},delay);
//!!
// $(window).focus(x=>{
// 	//$(window).off("focus");
// 	setTimeout(x=> { //try{
// 		forErasure=getHidElemsCmd("count");
// 		var sels=getHidElemsCmd(), nerased=$(".Web-Eraser-ed").length;
// 		log("WE focus, check for erasure", nerased ,forErasure,"site_erasedElems:",site_erasedElems);
// 		if (nerased < forErasure) { inner_eraseElements("focus"); }
// 		//}catch(e){console.error("WebEraser main--focus, error@",e.lineNumber,e);}
// 	},400);
// });
GM_addStyle( jqueryui_dialog_css()         //GM_getResourceText ("jqueryUiCss")
+" .sfswe-prevborder { border-color:transparent !important;border-width:"+border_width+"px !important;border-style:double !important; } "
+".sfswe-transparentborder  { border-color:transparent !important;border-width:"+border_width+"px !important;border-style:double !important; } "
+".sfswe-redborder { border-color:red !important; border-width:"+border_width+"px !important;border-style:double !important; } "
+"img.WebEraserCurtain { display: block !important; color:#fff !important; }"
+`.CurtainRod {
background-color: #bbb;
background-image: linear-gradient(90deg, rgba(255,255,255,.07) 50%, transparent 50%), linear-gradient(90deg, rgba(255,255,255,.13) 50%, transparent 50%), linear-gradient(90deg, transparent 50%, rgba(255,255,255,.17) 50%), linear-gradient(90deg, transparent 50%, rgba(255,255,255,.19) 50%);
background-size:  13px, 19px, 17px, 15px;
}`
+".ui-dialog-buttonpane button {color:black !important;}"
+'img[src*="blob:"] { display:block !important; }'
); // A later defined rule has precedence when both rules in effect.
//setTimeout(inner_eraseElements,1500);
if (plat_chrome && typeof submenuModule != "undefined") submenuModule.register("WebEraser"); //,"w");
regcmds();
setTimeout(reattachTornCurtains,4000);
gelems=$();
ttimer("end of main, state: "+document.readyState);
} catch(e){console.log("WebEraser main(), error@",e.lineNumber,e);}} //main()
function handleClick(e,iframe_click) {  try { //called from event handler in page & iframe, and pseudo called from click within iframe.
if (!e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
log("CLICK jwin is ","page:",location.href,"iframe?",iframe);
halter();
var permrm, target=e.target, frameEl=target.ownerDocument.defaultView.frameElement;
if(frameEl) target=frameEl;
prevDef(e);
if (!iframe_click) { //not pseudo call
var seltext_len=window.getSelection().toString().length;
window.status="webEraser, Ctrl-Click, on HTML element:"+target.tagName+" "+seltext_len+", ifame: "+iframe;
if (seltext_len != 0) { unhalt(); return; }
if (target.blur) target.blur();
if (iframe)  {
window.parent.postMessage( { type:"sfswe-iframe-click", code:0, src:location.toString() },"*"); // msg,origin makes pseudo call back here.
log("post to parent from window."); //"frameElement:",window.frameElement);
unhalt();
return false;
}
} // endif !iframe_click
while (/HTMLUnknownElement/.test(target.toString())) target=target.parentNode; //Avoid non HTML tags.
log("Click reached target",target);
if ($(target).is(".WebEraserCurtain"))
if (e.button==0) {
let reply=confirm("This will completely erase selected item, continue? \nOn any revisit to webpage you can check in the console for such erasures.");
if (reply) openCurtains("zap",$(target).siblings("img").addBack());
} else
eraseElementsCmd();
else if (!askedAlready) {
if ($("body").is(target)) {
if (!confirm("WebEraser.  You clicked on the main body of the webpage.  Body however, is not removable by ctrl-click, try ctrl-clicking on an image or other item on the webpage.  Hit CANCEL to open erasure window."))
eraseElementsCmd();
unhalt();
return;
}
halter.dialog=true;
inner_eraseElements();
var prom=checkIfPermanentRemoval(target);
prom.then(function(confirm_val){     // then takes a function with 2 params, the arg of resolve func call and of reject.
log("then in checkIfPermanentRemoval, permrm:",permrm,"confirm_val",confirm_val);
var [permrm,item_sel]=confirm_val;
halter.dialog=false;
unhalt();
if (permrm==Infinity)      { // temp delete
askedAlready=true;
alert("You hit TEMP, item deleted.  Ctrl-click from now until reload merely removes elements temporarily (esc to undo). ");
last_one_deleted=$(item_sel);
last_one_deleted.replaceWith("<placeholder delcnt="+(++delcnt)+">");
console.log("install escape catch");
escapeCatch(function(){
$("placeholder").replaceWith(last_one_deleted); },"perm");
}
log("checkIfPermanentRemoval, permrm",permrm);
//			if (permrm!=undefined)   inner_eraseElements("click");         //undefined==>escape (cancel)
if (permrm!=false)       inner_eraseElements("click");           //undefined==>escape (cancel)
});
prom.catch(function (a){log("caught in checkIfPermanentRemoval",a); unhalt();halter.dialog=false;});
} // endif !askedAlready
else {
last_one_deleted=$(target);  //temp delete
$("placeholder").remove();
last_one_deleted.replaceWith("<placeholder delcnt="+(++delcnt)+">");
console.log("set last_one_deleted:",last_one_deleted, "placeholder",$("<placeholder>"));
}
if(!halter.dialog) { unhalt();}
return false;
function halter() {	window.addEventListener("beforeunload", handlehalt);}
function handlehalt(event) {
event.returnValue="a  message to stay";
//log("event beforeunload: ",event.returnValue);
return event.returnValue;
};
function unhalt(){ 	window.removeEventListener("beforeunload",handlehalt,false);}
} catch(e) { console.log("Click handling error:"+(e.lineNumber),e,e.stack);unhalt(); }}  //handleClick()
function sprompt(tex,initv,cancel_btn="Cancel",ok_btn="OK",extra_btn){ // returns a promise with true/false value or for prompts an array value: [true/false,string], rejected with escape.
var dialog, p=new Promise((resolve,reject)=>{
dialog=sprompt_inner(tex,initv,resolve,reject,cancel_btn,ok_btn,extra_btn);
});
p.dialog=dialog;
return p;
}
function sconfirm(msg,cancelbtnText,okbtnText,extrabtnText) { return sprompt(msg,undefined,cancelbtnText,okbtnText,extrabtnText); }
function salert(msg) { return sprompt(msg,undefined,-1,"OK"); }
//Resolution of promise returned is cancel:false, OK: true, extrabtn: Infinity;
function sprompt_inner(pretext,initval,resolve,reject,cancelbtnText,okbtnText,extrabtnText) {try{ // "Cancel" has reply of false or null (if a prompt), "OK" gives reply of true or "", Escape key returns undefined reply.  undefined==null is true. but not for ""
var that=arguments.callee; if (that.last_dfunc) that.last_dfunc("destroy"); // Only one modal allowed.
var input_tag, input_style="width:80%;font-size:small;";
var confirm_prompt=initval===undefined;
if (!confirm_prompt) input_tag=initval.length<40 ? "input" : (input_style="width:95%;height:100px;","textarea");
var content=$("<div class=sfswe-content tabindex=2 style='outline:none;white-space:pre-wrap;background:#fff0f0;'>"
+"<div>"+pretext+"</div>"
+(initval!==undefined ? "<"+input_tag+" spellcheck='false' style='"+input_style+"'  tabindex='1'></"+input_tag+">":"")+"</div>");
content.find("input:not(:checkbox),textarea").val(initval);
try{content.resizable();}catch(e) {log("spromtinner(), err",e.lineNumber,e);}
var sp1=$(document).scrollTop();
var dfunc=content.dialog.bind(content);
var dialog=content.dialog({
modal: true, width:"auto", position: { my: "center", at: "center", of: unsafeWindow }, // Greater percent further to top.// Position is almost default anyway, difference is use of unsafeWindow due to strange error during prompt in jq in opera violentmonkey
close: function(e) { dialog.off("keydown"); $(document).scrollTop(sp1); if (e.key=="Escape") reject("Escape");}
}).parent();
var buttons={
[cancelbtnText]: function(e) { if (confirm_prompt) resolve(false); else resolve([false, $(this).find("input,textarea").val()]); dfunc("close"); return false;},
[okbtnText]: function(e) { if (confirm_prompt) resolve(true); else resolve([true,$(this).find("input,textarea").val() || ""]); dfunc("close"); return false;}
};
if(extrabtnText) buttons[extrabtnText]=function(e) { if (confirm_prompt) resolve(Infinity); else resolve([Infinity,$(this).find("input,textarea").val() || ""]); dfunc("close"); return false;};
content.dialog("option","buttons",buttons);
if (cancelbtnText==-1) { dialog.find("button").each(function(){   if (this.textContent=="-1") $(this).remove(); }); }
dialog.wrap("<div class=sfswe-sprompt></div>"); // allows css rules to exclude other jqueryUi css on webpage from own settings, a
dialog.keydown(function(e){	if (e.key == "Enter" && !/textarea/i.test(e.target.tagName)) $("button:contains("+okbtnText+")",this).click();  });
dialog.css({"z-index":2147483647, width:550, position:"fixed", left:200, top: 50, background: "whitesmoke"}); //"#fff0e0"
dialog.find(".ui-dialog-titlebar").remove(); // No img in css for close 'x' at top right so remove.  Title bar not in normal confirm anyhow.
dialog.draggable("option","handle", ".ui-dialog-buttonpane"); //
dialog.resizable();
// var maxH=innerHeight - (content.offset().top-$(window).scrollTop()) - 100;
// content.css({"overflow-x":"hidden","max-height":maxH}); //innerHeight-dialog.position().top-$(".ui-dialog-buttonpane").height()}).scrollTop(0);
setTimeout(function(){var ips=dialog.find("input,textarea");if (ips.length) ips.focus(); else content.focus();},100);
that.last_dfunc=dfunc;
return dialog; //.ui-dialog
}catch(e) {log("hlightAndsetsel(), err",e.lineNumber,e);}}
function checkIfPermanentRemoval(target) {   // called from click handler.
var sconfirm_promise, checkif_resolve, checkif_reject;
checkif_promise=new Promise((resolve,reject)=>{
checkif_resolve=resolve;checkif_reject=reject;
var parent=target.parentNode, index=0;
var msg="Permanently erase selected element(s) from website &mdash; now seen on page red bordered and blinking?  In addition you may use 'w' and 'n' keys freely, to widen and narrow your selection.  "
+"Escape quits.  Enter OK's.  Use the GM menu <a href='#abc"+Math.random().toString(36)+"'>Erase Web Elements</a> to edit internal code."           // Clickable link see .click below.
+"Hit Temp button below for ctrl-click to erase element(s) temporarily and inhibit this prompting until reload."
+"\n\nInternal code for <span id=fsfpe-tagel></span><br><div style='display:inline-block; position:relative;width:100%'><input disabled id=sfswe-seledip style='width:80%;margin:10px;'><div id=sfswe-seledipfull style='position:absolute; left:0; right:0; top:0; bottom:0;'></div></div>";
$(document).keypress(keypressHandler);
sconfirm_promise=sconfirm(msg,"Cancel","OK","Temp"); ////////////////
var dialog=sconfirm_promise.dialog;
var buttonpane=dialog.find(".ui-dialog-buttonpane");
buttonpane.append("<div><input id=sfswe-checkbox7 type=checkbox style='vertical-align:middle'>"+"<label style='display:inline'>&nbsp;&nbsp;Remove just from this page (not entire website).</label></div>");
buttonpane.append("<br><div style='margin-top:-10px;'><input id=sfswe-checkbox6 type=checkbox style='vertical-align:middle'>"	+"<label style='display:inline;'>&nbsp;&nbsp;Completely delete element.</label></div>");
dialog.find("a").click(e=>{
log("click on a in OK");
dialog.trigger($.Event("keydown",{keyCode:27,key:"Escape"})); // close prompt.
eraseElementsCmd();});
var input=$("#sfswe-seledip"), ip=input[0], div_surround=input.next();
div_surround.click(e=>{ // a click on input & surround enables it.
ip.disabled=false; 	    ip.setSelectionRange(999,999);
div_surround.css("display","none");
input.focus();
input.blur(e=>{ip.disabled=true; div_surround.css("display",""); });
}); //
hlightAndsetsel(target); // Also sets input value to selector!
setTimeout(function(){dialog[0].scrollIntoView();},100);
});//new Promise()
close_of_prompt(sconfirm_promise, checkif_resolve, checkif_reject);
log("Got back from close_of_prompt, close_of_prompt:",sconfirm_promise);
return checkif_promise;
}//end checkIfPermanentRemoval()
function close_of_prompt(sconfirm_promise, checkif_resolve, checkif_reject) {
var nested_confirm, first_reply, complete_rm, reply_sel;
sconfirm_promise.catch(function(reply){
hlightAndsetsel(0,"off","restore");
log("caught confirm prom");
checkif_reject("caught");
});
nested_confirm=sconfirm_promise.then(function(reply){                 ///////////////////////////////////////
$(document).off('keypress');
$(":data(pewiden-trace)").data("pewiden-trace",""); // remove trace
var complete_rm=$("#sfswe-checkbox6:checked").length!=0;
var webpage_only=$("#sfswe-checkbox7:checked").length!=0;
log("complete_rm?",complete_rm, "reply:",reply);
if (reply!=false) reply_sel=$("#sfswe-seledip").val().trim();
if(reply!=true) {
hlightAndsetsel(0,"off","restore");
//if(reply==Infinity) checkif_resolve([reply,$(reply_sel).detach]); //$(reply_sel).hide(); // temp delete
checkif_resolve([reply,reply_sel]);
return;
};
sconfirm_promise.data=[reply_sel,complete_rm]; // use ES6 await?
if (reply_sel)  {
let ancErased=$(reply_sel).closest(".Web-Eraser-ed");  //.closest, includes current.
if (hidElementsListCmd("isthere?", reply_sel) || (ancErased.length && getHidElemsCmd("match el",ancErased))){
alert("Already attempting erasure of the element specified or parent, if not being erased properly try "
+"ticking the monitoring option or open 'Erase Web Elements' GM menu and hit its 'OK' button.\nInternal code:"
+reply_sel+"\n\n   Ancestor:"+nodeInfo(ancErased));
console.info("Already erasing",ancErased,nodeInfo(ancErased),".  Your selector",reply_sel);
hlightAndsetsel(0,"off","restore");
checkif_reject("Ancestor Already");
return;
}
if (!webpage_only)
hidElementsListCmd("add",reply_sel+" site");
else
hidElementsListCmd("add",reply_sel);    // btn1 -> null, btn2 -> "<string>" null==undefined
if (hidElementsListCmd("rm", $(reply_sel).find(".Web-Eraser-ed"))) console.info("Removed child selectors of",reply_sel);
hlightAndsetsel(0,"off","restore");
log("reply true, complete_rm@",complete_rm,"add sel:",reply_sel);
if (complete_rm) zaplists.add(reply_sel);
checkif_resolve([true,reply_sel]);
} else {  // empty reply.
hlightAndsetsel(0,"off","restore");
checkif_reject("empty");
}
});// sconfirm_promise.then        /////////////////////////////////////////////
}
function eraseElementsCmd() {
// Called from GM script command menu and from clickable within ctrl-click  prompt.
//
var erasedElems, no_sels;
erasedElems=getHidElemsCmd("with site");
no_sels = !erasedElems ? 0 : erasedElems.split(/,/).length;
var prompt_promise=sprompt(
"See checkboxes distantly below to set the script's configutation values.  Ctrl-click is the usual way to erase parts of a webpage, however, as an alternative below you can manually "
+"edit the internal selectors for erased elements, eg, 'DIV#main_column site', optional word 'site' erases the element at the entire website; eg, 'iframe' will hide all iframes (often ads). "
+"For more than one selector use commas to separate"+(no_sels?", currently there's "+no_sels+" below":"")+".  To remove all element erasures set to blank.  Reload webpage if necessary."
, erasedElems.replace(/,/g,", \n"));
prompt_promise.then(function([btn,reply]){
if (!btn) return; //cancel ==> null, undefined==> escape. (null is == to undefined!)
config={monitor:config.monitor}; delete config.monitor[website];
if ($("#sfswe-checkbox:checked").length)	config.noAnimation="checked";
if ($("#sfswe-checkbox2:checked").length)	config.keepLayout="checked";
if ($("#sfswe-checkbox3:checked").length) 	config.hideCurtains="checked";
if ($("#sfswe-checkbox5:checked").length)       config.monitor[website]="checked";
if ($("#sfswe-checkbox4:checked").length) {
toggleCurtains();
let subpromt=sprompt("Please enter http address of curtain image to be used.  If giving left and right images separate with a space.  "
+"Leave empty to reset.  Accepts base64 image strings.","");
subpromt.dialog.attr("title","Perhaps try a quaint example; one found with an image search for 'curtains':\n\thttp://www.divadecordesign.com/wp-content/uploads/2015/09/lace-curtains-5.jpg");
subpromt.then(function([btn2,reply2]){
if (btn2) { setValue("ownImageAddr",reply2);
curtain_icon=reply2||whitecurtains;
curtain_slim_icon=reply2||whitecurtainsoriginal;
curtain_wide_icon=reply2||whitecurtainstriple;
$(".WebEraserCurtain").attr("src",curtain_icon);  }
toggleCurtains(); });
} else {
let duplicates={};
reply=reply.replace(/\s*,\s*/g,",").replace(/(?=[^,])\n(?=[^,])/g,",").split(/,/); // , newline->comma if none; if no comma all is put in [0]
log("Got reply array:",reply);
if(reply.length) {
page_erasedElems=[]; site_erasedElems=[];
$(reply).each((i,str)=>{ //
if (str=="") return;
if (duplicates[str]) return;
duplicates[str]=true;
str=str.trim();
if (/\ssite$/.test(str)) site_erasedElems.push(str.replace(/\ssite$/,""));
else page_erasedElems.push(str);
});
site_erasedElems=site_erasedElems.toString();
page_erasedElems=page_erasedElems.toString();
}
try{$(reply);} catch(e){alert("Bad selector given."); throw(e);}
setValue("config",config);
setValue(website+":erasedElems",site_erasedElems);
setValue(webpage+":erasedElems",page_erasedElems);
zaplists.update();
//log("end awaaits of setvalue");
openCurtains();
$(".Web-Eraser-ed").each(function(){
var self=$(this);
self.css({display: self.data("sfswe-display"), visibility: self.data("sfswe-visibility")});
self.removeClass("Web-Eraser-ed");
});
$(".CurtainRod").remove();
//log("set tout")
setTimeout(inner_eraseElements,1000,"prompt"); //'cos openCurtains takes time
//inner_eraseElements("fromPrompt");
}
});//prompt_promise.then()
var keep_layout=config.keepLayout;
var dialog=prompt_promise.dialog;
dialog.find(".ui-dialog-buttonpane").prepend(
"<div class=sfswe-ticks style='float:left;font-size:10px;'>" //width:78%;
+"<input id=sfswe-checkbox2 type=checkbox style='float:left;"+(!keep_layout?" margin:0 3px;":"")+"' "+keep_layout+"><label>Preserve layout (in general).</label>"
+(keep_layout ? "<input id=sfswe-checkbox3 type=checkbox style='margin:0 3px 0 10px;height:12px;'"+(config.hideCurtains||"")+"><label>Also hide curtains.</label>" : "")
+"<br><input id=sfswe-checkbox type=checkbox style='margin-left:3px;'"+(config.noAnimation||"")+"><label>Disable animation (in general).</label>"
+"<br><input id=sfswe-checkbox4 type=checkbox style='margin-left:3px;'><label>Set your own curtains' image.</label>"
+"<input id=sfswe-checkbox5 type=checkbox style='margin-left:15px;'"+(config.monitor[website]||"")+"><label>Monitor for new elements on this website.</label>"
+"</div>"
);
dialog.find("input:checkbox").css({
//cmtd out 8/17	"-moz-appearance":"none",
height:12});
dialog.find(".ui-dialog-content").attr("title","WebEraser userscript.\n"+webpage+"\n\nCurrent matches at this webpage:\n"+bodymsg());
} //eraseElementsCmd()
function inner_eraseElements(from) {
//
// Called at page load and when user sets selector(s) for erasure.
// 1. Go through uncurtained elements for erasure and do curtainClose (or css display to none) on each.
// 2. Class each as "Web-Eraser-ed" and backup css values that might get changed.
// 3. If changes were made log details to console and to logging div.
//
var erasedElems=getHidElemsCmd(), len=erasedElems.length, erasedElems_ar=erasedElems.split(/,/), count=0, nomatch=[];
if (erasedElems_ar[0]=="") erasedElems_ar.shift(); //fix split's creation of array length one for empty string.
var theErased=$(".Web-Eraser-ed"); theErased.removeClass("Web-Eraser-ed");
log("inner_eraseElements, erasedElems_ar",erasedElems_ar, "from:",from);
erasedElems_ar.forEach(function(sel,i){
erasedElems=$(sel); //Array.from(document.querySelectorAll(sel)); //$(sel), jQ cannot find duplicate ids.
erasedElems.each(function() {
log("inner_eraseElements Found for sel",sel," el: ",this);
var eld=this,el=$(eld); // 40msecs per 'each' loop.
if(/delay|focus/.test(from)) { // skip curtains already closed.
var crod=jQuery.data(eld,"rod-el"); //el.prev()[0];
if (crod && /^sfswediv/i.test(crod[0].tagName)) {el.addClass("Web-Eraser-ed");return;}
}
markForTheCurtains(el,eld,sel);
var no_anima=config.noAnimation, keep_layout=config.keepLayout;
if (no_anima && !keep_layout)  eld.style.setProperty("display","none","important");
else  if (el.css("display")!="none") closeCurtains(el, no_anima, measureForCurtains);
count++;
}); //erasedElems.each()
if (erasedElems.length==0 && sel) nomatch.push(sel);
}); //forEach()
if (iframe || count==0) return;
theErased=$(".Web-Eraser-ed");
observeThings();
if (len==0)  observeThings("off");
if (theErased.length==0) return;  ////////////////////
if (nomatch.length) {
console.info("WebEraser message: no match for the following selectors at",webpage+":");
nomatch.forEach(nom=>console.info("\t",nom));
}
var ieemsg="Userscript WebEraser has selectors to hide.  "+count+(count==1 ? " element that was":" elements that were")+" present on page at site: "+website
+".\nSee GM menu command Erase Web Elements to check and edit selector list.  "
+(config.keepLayout ? "" : "Keep layout is not ticked.")
+(config.noAnimation ? "Animation is off." : "")
+(config.hideCurtains ? "Hide curtains is ticked." : "");
theErased.each(function(i){
var that=$(this);
var sel=that.attr("selmatch-sfswe");
var onzaplist=zaplists.which(sel);               // 10 msecs to here from prev in  closeCurtains() above.
var rod=jQuery.data(this,"rod-el");
var is_an_overlay=rod && rod.hasClass("sfswe-overlay");    //that.prev().hasClass("sfswe-overlay");
ieemsg+="\n"+(i+1)+":"+sel;
ieemsg+=".\t\t"
+(is_an_overlay ? "=> Considered as an Overlay,takes up > 2/3 of window, deleted."
: onzaplist.zap ? " => complete erasure."
: onzaplist.keep_layout ? " => erase but keep layout."
: "" );
});
ieemsg+="(phase:"+from+")";
count=0;
console.info(ieemsg);
bodymsg(ieemsg.replace(/(.*\n){2}/,"")+"  Whence: "+from+".","init");
}
function closeCurtains(el, noAnimKeepLayout, finishedCB=x=>x) {   //called from inner_eraseElements()
//log("closeCurtains1, el:",el,noAnimKeepLayout,finishedCB,"sel:",el.attr("selmatch-sfswe")); //,"\n\nLog of Stack",logStack());
var that=closeCurtains; if (!that.final_curtain) that.final_curtain=0;
var hide_curtains=config.hideCurtains, keep_layout=config.keepLayout, wediv=el.children("sfswediv"), curtainRod, lrcurtains;
var old_curtained=wediv.length ? jQuery.data(wediv[0],"covered-el") : null;
if ( ! old_curtained || ! old_curtained.is(el))
[curtainRod,lrcurtains]=createCurtains(el,noAnimKeepLayout);
else { curtainRod=el.children("sfswediv"), lrcurtains=curtainRod.children();}
curtainRod.css("display","");
var onzaplist=zaplists.which(el); // 20 msecs from prev
if (noAnimKeepLayout) {
lrcurtains.css({width:"51%"});
if (onzaplist.zap) { curtainRod.css({display:"none"}); el[0].style.setProperty("display","none","important");} // "none" triggers monitor if on.
else if (onzaplist.keep_layout||hide_curtains||curtainRod.hasClass("sfswe-overlay")){
curtainRod.css({visibility:"hidden",display:""});
el[0].style.setProperty("visibility","hidden","important");
}
measureForCurtains();
}
else { // Do animated curtain closing, then, perhaps, fade out.
that.final_curtain++;
manimate(lrcurtains,["width",15,"%"],1000,2);
manimate(lrcurtains,["width",51,"%",1000],1000,5,function(){ ///////////////////////Animation
log("Anim end",lrcurtains,"Width of left curtain:",lrcurtains.css("width"));
lrcurtains.css("width","51%");
curtainRod.css("visibility","visible");
el=jQuery.data(this.parentNode, "covered-el")||$();
if (!keep_layout || curtainRod.hasClass("sfswe-overlay")||onzaplist.zap) {
//console.log("Anim end, fade curtains");
el.add(curtainRod).delay(200).fadeOut(       // $.add here prepends.
500, function(){
this.style.setProperty("display","none","important"); // triggers monitor if on.
if (el[0]==this && --that.final_curtain==0) finishedCB();
});
}
else if (hide_curtains||onzaplist.keep_layout) {
//console.log("Anim end, fade out 2");
el.add(curtainRod).delay(200).fadeOut(
1000, function(){
this.style.setProperty("visibility","hidden","important");
this.style.setProperty("display",$(this).data("sfswe-display"),"important"); //triggers monitor.
curtainRod.css({visibility:"hidden",display:""});
curtainRod.remove();
if (el[0]==this && --that.final_curtain==0) finishedCB();
});
} else if (--that.final_curtain==0) {
//console.log("Anim end, call CB");
finishedCB();
}
}); //animate()
}
return false;
} //closeCurtains()
function keypressHandler(event) { try{ //while prompt is open.
var ip=$("#sfswe-seledip:enabled");
if (ip.length) { //live typing of selector.
setTimeout(ip=>{
var cval=ip.val(), matched_els=[];
try{matched_els=$(cval);} catch(e) {};// bad selector, transient
if (matched_els.length) { // may unwind.
hlightAndsetsel(0,"off",null,"mere_highlight");
hlightAndsetsel($(cval),null,null,"mere_highlight"); }
else hlightAndsetsel(0,"off","restore");
},500,ip);
} else  { // widen/narrow
switch(event.key) {
case "w": widen(); break;
case "n": narrow(); break;
default: return; }
return false;
}
} catch(e) {console.error("An key handler error:"+e+" "+e.lineNumber);} };
// GM_registerMenuCommand("Temporary web deleter, ctrl-click",function(){
//     window.addEventListener("mousedown",function(e){
// 	if(e.ctrlKey) {
// 	    if (e.preventDefault) { e.preventDefault(); e.stopPropagation(); }
// 	    e.target.style.setProperty("display","none","important");
// 	}
//     },true);
// });
//thousand's comma, call Number.toLocaleString()
//if (iframe) console.log=x=>null;       //logger(); // Logs from doc start.
async function init_globs() { // all globs asynchronously set.
log("init_globs");
page_erasedElems=(await getValue(webpage+":erasedElems","")).trim();
//loader Failed here.
site_erasedElems=(await getValue(website+":erasedElems","")).trim();
elems_to_be_hid=getHidElemsCmd();
zaplists=new zaplist_composite();
await zaplists.update(); //depends on site/page_erasedElems being read first.
ownImageAddr=await getValue("ownImageAddr","");
whitecurtains=await GM.getResourceUrl("whiteCurtains");
// Ensure visit to https matches getResourceUrl use of https or address as given in header w/wo ssl!
whitecurtainsoriginal=await GM.getResourceUrl("whiteCurtainsOrig");
whitecurtainstriple=await GM.getResourceUrl("whiteCurtainsTrpl");
curtain_icon=ownImageAddr|| whitecurtains;
curtain_slim_icon=await getValue("ownImageAddr","")||whitecurtainsoriginal;
curtain_xslim_icon=await getValue("ownImageAddr","")||await GM.getResourceUrl("whiteCurtainsXsm");
curtain_wide_icon=await getValue("ownImageAddr","")||whitecurtainstriple;
config=await getValue("config",{keepLayout:"checked",monitor:{}});
if (!config.monitor) config.monitor={};
if(!jQuery.ui)  { // GM4 loader problem. Another userscript is loaded that has jq but no jQuery-UI will clobber $.ui here.
$=jQuery=jq_saved;  // saved from when prior to async branch.
console.log("GM4 FAILure, jq.ui clobbered, patching up.",jQuery.ui);
}
}
function installEventHandlers(phase2) {
if(!phase2) {
document.addEventListener("scroll", function(e){ if (!overlay) return; e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();},true);
//window.addEventListener("click",handleClick,true);
window.addEventListener("mousedown",handleClick,true);
window.addEventListener("message", postMessageHandler,false);
if(iframe) window.installedEHs=true;
//console.log("installed handlers, window.mousedown, at:",location.href,".  In iframe?",iframe);
}
else {
$("iframe").each(function(){
var fwin=this.contentWindow; try{ //perhaps permission error due to iframe origin.
if (!fwin.installedEHs) {
fwin.addEventListener.call(fwin,"mousedown",handleClick,true);
fwin.addEventListener.call(fwin,"message", postMessageHandler2,false);
//despite use of call(), event is still triggered in this context not in iframe's hence use of frameElement.
}} catch(e){};
});
}
}
function prevDef(e) { if (e.preventDefault) {	e.preventDefault();   e.stopPropagation(); e.stopImmediatePropagation();  }
} //else console.log("No preventDefault for event",e);	}
function postMessageHandler(e){ //reads postMessage().
if ( ! e.data.type || e.data.type!="sfswe-iframe-click") return;
//log("Handle a PostMessage, iframe:",iframe, "code:",e.data.code,"data",e.data,[e]);
if (iframe) {
window.parent.postMessage({type:"sfswe-iframe-click",code:++e.data.code},"*");
return;
}
var iframeEl;
$("iframe, embed").each((i,el)=>{
// log("cmp, el.cont",el.contentWindow," e.source",e.source," e.origin",e.origin,typeof e.origin);
// log("getsvg",el.getSVGDocument,el);
// log("src",e.data.src,"==",el.src);          //xss perm denied, e.source.toString(),
if (el.contentWindow==e.source ||e.data.src==el.src) { iframeEl=$(el); return false; }
});
// var iframeEl=$("iframe, embed").filter(function(){
// 	log("cmp, this.cont",this.contentWindow," e.src",e.source,"res:",this.contentWindow==e.source);
// 	return this.contentWindow==e.source;
// });
handleClick({target:iframeEl[0],ctrlKey:true},"iframe_click");
}
function getSelectorWithNearestId(target,exclude_classes) { // need extended jquery for :regexp
ensure_jquery_extended();
var sel, nearestNonNumericId=target.closest(":regexp(id,^\\D+$)").attr("id"), nnmi=nearestNonNumericId; //closest also checks target
//console.log("nnmi",nnmi, "matches #els:",$("[id="+nnmi+"]").length);
if (nnmi && $("[id="+nnmi+"]").length>1) { nnmi="";ignoreIdsDupped=true;} // Page error duplicate ids, ignore id.
if (nnmi) nnmi=$("#"+nnmi).prop("tagName")+"#"+nnmi; //cos of jQ & multiple ids.
if ($(nnmi).is(target)) sel=nnmi;
else {
sel=selector(target,$(nnmi),true,0,exclude_classes); //ok if nnmi is undefined id.
if (!sel) sel=nnmi; //both target and $(nnmi) are same element.
else if(nnmi) sel=nnmi+sel;
}
return sel;
}
function getHidElemsCmd(cmd,el){
var els, pels=page_erasedElems, sels=site_erasedElems;
//console.log("Got sels as:",sels);
switch(cmd) {
case "match el":  return el.is($(getHidElemsCmd()));
case "count":     return getHidElemsCmd().split(/,/).reduce(function(prev_res,sel){return prev_res+$(sel).length;},0);
case "with site": sels=sels.replace(/,/g," site,")+(sels ? " site" : ""); // see reverse of this in hidElementsListCmd() and  eraseElementsCmd().
default:          return pels + (sels && pels ? "," : "") + sels;	// if (justpels_ar) return pels.split(","); //webpage elements.
}
}
function hidElementsListCmd(cmd,str,str2) {
//console.log("hidElementsListCmd, cmd:",cmd, "str:",str,"str2:",str2, "HidElems:",getHidElemsCmd());
var  sitewide;
switch(cmd) {
case "add":
if (hidElementsListCmd("isthere?",str)) return;
if (/\ssite$/.test(str)) { sitewide=true; str=str.replace(/\s+site$/,"");  }
if (sitewide)  site_erasedElems += site_erasedElems ? ","+str : str;
else  page_erasedElems += page_erasedElems ? ","+str : str;
$(str).each(function() {  $(this).data("sfswe-oldval", $(this).css(["display","visibility","height","width"]));	});
break;
case "mv":
if (hidElementsListCmd("rm",str)) str2+=" site";
hidElementsListCmd("add",str2);
return; //return needed to prevent saving of old values.
case "rm":
if (str instanceof $) { str.each(function(){ hidElementsListCmd("rm", $(this).attr("#selmatch-sfswe"));	});  return str.length; }
page_erasedElems=$.map(page_erasedElems.split(/,/),el=>el==str ? null : el.trim()).join(",");
site_erasedElems=$.map(site_erasedElems.split(/,/),el=>el==str ? null : el.trim()).join(",");
break;
case "isthere?": //check if str is amongst hidden elements list.
return getHidElemsCmd().split(/,/).includes(str);
//return getHidElemsCmd().split(/,/).reduce((prev_res,next)=>prev_res||next==str,false);
}
log("hidElementsListCmd",cmd,str,str2,"SetValues: ",site_erasedElems,page_erasedElems);
setValue(website+":erasedElems",site_erasedElems);
setValue(webpage+":erasedElems",page_erasedElems);
zaplists.update();
return sitewide;
}
//Blinks are double, one for selected elements, other is only when at top/bottom of narrow/widen chosen.
function hlightAndsetsel(elem, off, restore, mere_highlight) {try{ //also updates prompt with elem's selector.
if (!off) { // on
elem=$(elem);
if (elem.length==0) return;
gpre_elem=gelem;
gelem=$(elem);
var newsel,fullsel,h=gelem.height(),w=gelem.width();
console.info("W/e widen/narrow, element to highlight is",gelem, mere_highlight);
if (!mere_highlight) { // not typed in but from widen/narrow etc.
var selinput=$("#sfswe-seledip"),            //sfs_pesel");
elhtml=gelem[0].outerHTML.replace(gelem[0].innerHTML,"");
newsel=getSelectorWithNearestId(gelem,tbcl+" "+rbcl+" Web-Eraser-ed");
fullsel=selector(gelem,0,false,0,tbcl+" "+rbcl+" Web-Eraser-ed");
gelems=$(newsel).not(gelem);
selinput.val(newsel); //+"<pre style='font-size:14.4px;'>\n\tHTML in pre</pre>");
selinput.prop("title", (newsel!=fullsel ? "Full selector:\n\n\t"+fullsel+"\n\n" : "")
//+gelem[0].outerHTML.replace(/>.*/g,">").replace(/\s*</g,"<")
+"Element html:\n"+elhtml
+"\n\nElement style:\n"+myGetComputedStyle(gelem[0]));
} //endif !mere_highlight
updatePromptText(newsel,fullsel);
gelem.data("pewiden-trace","true"); //    if (!gelem.hasClass("pewiden-trace"))
//!!	gelem.parents().addBack().addClass(tbcl);
//	gelem.find(">:only-child").addClass(tbcl);
gelem.add(gelems).toggleClass(rbcl);
gelem.elh=gelem[0].style.height;	gelem.elw=gelem[0].style.width;
gelem.height(h- 2*border_width);gelem.width(w- 2*border_width);
bblinker=setInterval(function(){ // normal "selected" blink.
if (gelems.length) gelems.toggleClass(rbcl);
else gelem.toggleClass(rbcl);    //.css({borderColor:"red",borderWidth:"9px",borderStyle:"double"});
if (plat_msedge) // catch of escape not working on msedge.
if ($(".ui-dialog").css("display")=="none") 	hlightAndsetsel(0,"off","restore");
},1200);
}
else { //off
clearInterval(bblinker);
gelem.removeClass(rbcl);
gelem[0].style.height=gelem.elh;	gelem[0].style.width=gelem.elw;
//gelem.height(h+ 2*border_width);gelem.width(w+ 2*border_width);
if (restore) $("."+tbcl).removeClass(tbcl);
}
}catch(e) {console.error("hlightAndsetsel(), err",e.lineNumber,e);}}
function widen() { // .html() return &gt; encodings, .text() does not.  tab as @emsp must be set with html() not text()
var selinput=$("#sfswe-seledip");
if (/[:.][^>]+$/.test(selinput.val())) {
var newsel=selinput.val().trim().replace(/[:.][^:.]+$/,"");
selinput.val(newsel);
gelems=$(newsel);
gelems.addClass(rbcl);
updatePromptText();
return;
}
if (gelems.length) { gelems.removeClass(rbcl);gelems=$();}
var p=gelem.parent();
if (p.is("body")) {
blinkBorders(gelem); //blink double indicates top of hierarchy.
return;
}
hlightAndsetsel(0,"off");
hlightAndsetsel(p);
}
function narrow() {
if (gelems.length) {
widen();  // nulls gelems.
narrow(); // Follow gelem trace back to el.
//narrow();
return;
}
var trace=gelem.find(":data(pewiden-trace):first"); // trace left by hlightAndsetsel()
if(trace.length==0) trace=gelem.find(">:only-child");
if (trace.length==0) {
blinkBorders(gelem);
return;
}
hlightAndsetsel(0,"off");
hlightAndsetsel(trace);
}
function updatePromptText(newsel,fullsel) { 	// set text size tagname etc.
var updated_text="";
if (gelems.length<=1)
updated_text="selected ("+gelem.prop("tagName").toLowerCase()+") element ("+(gelem.height()|0)+"x"+(gelem.width()|0)+"pixels)";
else
updated_text="selected "+gelems.length+" "+gelem.prop("tagName").toLowerCase()+"s";
updated_text+=":";
$("#fsfpe-tagel").parent().prop("title","Click here to invoke widen/narrow with 'w' and 'n' keys resp.\nClick on the internal code below, then move mouse a small bit to see "
+(newsel!=fullsel ? "full position in hierarchy," : "")
+" html and style settings of the selected element. ");
$("#fsfpe-tagel").text(updated_text);
}
function myGetComputedStyle(el) {
if (!document.defaultView.getDefaultComputedStyle) return ""; // has no getDefaultComputedStyle().
var roll="",defaultStyle=document.defaultView.getDefaultComputedStyle(el);
var y=document.defaultView.getComputedStyle(el), val, val2, i=1;
for (let prop in y) {
if (/^[a-z]/.test(prop) && ! /[A-Z]/.test(prop) && (val=y[prop])
&& val!=defaultStyle[prop]) {
if (val.trim)  //just a type check
if (val.startsWith("rgb")) val="#"+val.replace(/[^\d,]/g,"").split(/,/).map(x=>Number(x).toString(16)).join("");
if (prop.startsWith("border") && y[prop.replace(/-\w*$/,"")+"-style"]=="none") continue; // Error in getDefaultComputedStyle borders not set properly (eg, color should be that of el)
roll+= prop +": "+val+"; ";
if (i++%3==0) roll+="\n";
} //endif
}
return roll;
}
function blinkBorders(elem, interval=150, times=4) { // borders must already be set.
times*=2;
var cnt=0,i=setInterval(function(){
cnt++;
elem.toggleClass(rbcl);
//!!
//	elem.toggleClass(tbcl);
if (cnt==times) {clearInterval(i);elem.removeClass(rbcl);}// interference so rm class.
},interval);
}
function ensure_jquery_extended() {
if ($.expr[":"].regexp) return; // already extended, not yet clobbered.
$.fn.reverse = Array.prototype.reverse;
$.fn.swap = function(to) {
var a=this.eq(0), b=$(to).eq(0);
var tmp = $('<span>').hide();
a.before(tmp);
b.before(a);
tmp.replaceWith(b);
return;
};
$.easing["stepper"] =  function (x, t, b, c, maxt) { // eg, see, console.log($.easing)  for other funcs.
// var y=c*(t/=maxt)*t + b;
// if (x<0.4) y=0.1;
//console.log(x);
//return y;
return x;
};
$.extend($.expr[':'], {       // Check it's there with $.expr[":"].regexp.toString()
regexp: function(currentobj, i, params, d) { //filter type function.
params=params[3].split(/,/);       //eg, [ 'regexp', 'regexp', '', 'className,promo$' ]
var attr=params[0], re=params[1];  //eg, className, promo$
if (attr=="class") attr="className";
var val=currentobj[attr]+""||"";
if (attr=="className") return val.split(/\s/).some(function(cl){return cl.match(re);});
else return val.match(re);
}}); //$.extend()     //usage eg: $(“div:regexp(className,promo$)”);
(function($){
$.event.special.destroyed = {
remove: function(o) {
if (o.handler) {
o.handler();
}
}
}; })($); //Usage: $("#anid").bind('destroyed', function() {// do stuff}) // only for is jQ  removed el.
} //extend_jquery()
function selector(desc,anc,no_numerals,recursed,exclude_classes) {try{ // descendent, ancestor, such that ancestor.find(ret.val) would return descendant.  If no ancestor given it gives it relative to body's parent node.   // See example usage in checkIfPermanentRemoval(). Numeraled classes/ids are excluded.
anc=$(anc).eq(0); //apply only to first ancestor.
if (anc.length==0) anc=$(document.body.parentNode); // !anc wouldnt work for a jq obj.
desc=$(desc);
if ( (desc.closest(anc).length==0 || desc.length!=1) && !recursed) {
console.info("Too many elements or descendant may not related to ancestor:");
console.info("Descendant is:"+selector(desc,0,0,true));
console.info("Ancestor is:"+selector(anc,0,0,true)+".");
return;
}
// Last element is highest in node tree for .parentsUntil();
var sel=
desc.add(desc.parentsUntil(anc)) // up to but not including.
.reverse()
.map(function() { // works from bottom up to ancestor, hence need for reverse().
var t=$(this), tag=this.tagName.toLowerCase(), nth=t.prevAll(tag).length+1, id="", cl, nthcl;
//id=this.id.replace(/^\s*\b\s*/,"#"); if (!ignoreIdsDupped) id="";
cl=(t.attr("class")||"").trim(); // Don't use this.className (animated string issue)
cl=cl.split(/\s+/).join(".").prefix(".");
if (exclude_classes) cl=cl.replace(RegExp(".("+exclude_classes.replace(/ /g,"|")+")","g"),"");
if (no_numerals && /\d/.test(id)) id="";
if (no_numerals && /\d/.test(cl)) cl="";
if ( (cl && t.siblings(tag+cl).length==0)
|| id
|| t.siblings(tag).length==0)
nth=0;
else if (cl && t.siblings(tag+cl).length!=0) {
cl+=":eq("+t.prevAll(tag+cl).length+")";   //jQuery only has :eq()
nth=0;
}
return tag+(nth?":nth-of-type("+nth+")":"")+id+cl; ////////////////////nth-of-type is One-indexed.
}) //map()
.get()         //
.reverse()
.join(">");
if (desc.is(anc.find(">"+sel))) {
if (anc.is(document.body.parentNode)) return "html>" + sel;
return ">"+sel;
} else {
console.info("Selector result:\n\t"+sel+"  Not findable in ancestor, nor in body's parent.");
if ($(sel).length) return sel; //Its the very top element, <HTML>.
}} catch(e){logError("Can't get selector for "+desc,e); }}  //fixBadCharsInClass(desc);}
function fixBadCharsInClass(obj) { //official chars allowed in class, throw error in jquery selection.
obj.parents().addBack().each(function(){ this.className=this.className.replace(/[^\s_a-zA-Z0-9-]/g,""); });
}
function markForTheCurtains(el,eld,sel,unmark) {
if (!unmark) {
el.css({overflow:"hidden"}).addClass("Web-Eraser-ed").attr("selmatch-sfswe",sel) //hidden, so height not 0.
.data({sfsweDisplay: eld.style.display, sfsweVisibility:eld.style.visibility, sfsweOverflow: eld.style.overflow}); // needed in case zero height element with floating contents. // To make it have dims, in case of zero height with sized contents.
}
else el.css({overflow:el.data("overflow")}).removeClass("Web-Eraser-ed").attr("selmatch-sfswe",""); //hidden, so height not 0.
log("end markForTheCurtains, classes:",eld.className);
}
function reattachTornCurtains(curtains=$(".CurtainRod")) {try{
var torn=false;
curtains.each(function(){
var that=$(this), el=jQuery.data(this,"covered-el")||$();
if (el.parent().length==0 || !el.hasClass("Web-Eraser-ed")) {
torn=true;
that.addClass("sfswe-delete","true");
//that.remove();
}    });
$(".sfswe-delete").remove();
if (torn) inner_eraseElements();
} catch(e){console.error("WebEraser reattachTornCurtains(), error@",e.lineNumber,e);}}
function measureForCurtains(curtains=$(".CurtainRod")) {
curtains.each(function(){
//console.log("measureForCurtains for el(data-covered) as:",jQuery.data(this,"covered-el"));
var that=$(this), el=jQuery.data(this,"covered-el");          // $.data seems to lose its info when another userscript is also running, jQuery.data works.
if(!el) {console.error("noel");el=$();}
var w=el.outerWidth(), h=el.outerHeight()+1; // Includes padding & border, margin included if 'true' passed.  jQuery sets and unsets margin-left during this, provoking attrModifiedListener.
if (!el.hasClass("Web-Eraser-ed")) {
el.addClass("Web-Eraser-ed");
el.css({overflow:"hidden"});
}
//var offset=moffset(el);
//that.css(offset);
//that.css({height:h,width:w});
//this.style.setProperty("width",w+"px","important");
if(!that.hasClass("outie")) {
that.css({left:0,top:0});
this.style.setProperty("width","100%","important");
this.style.setProperty("height","100%","important");
}else {
var offset=moffset(el);
that.css(offset).css({height:h,width:w});
this.style.setProperty("width",w+"px","important");
}
});};
function bodymsg(str,init) { // Append string to a <pre> that is initially appended to body.
if ($("#sfswe-div-logger").length==0) $("body").prepend("<pre style='display:none;' id=sfswe-div-logger><pre class=init></pre></pre>");
var sfsprelog=$("#sfswe-div-logger");
var initpre=sfsprelog.find(".init");
if (str) if (init) initpre.text(initpre.text()+"\n"+str+"\n");        //b.attr("sfswe-message",str);bodymsg.init=str;}
else {
if (str==bodymsg.str) 	sfsprelog.append(".");
else {
sfsprelog.append("\n"+str);
console.info("WebEraser Monitor: "+str);
bodymsg.str=str;
}
}
return initpre.text();
}
function observeThings(disable) { // call will start or if running reset monitoring, with param, it disables.
var that=arguments.callee; that.off=[];
if (that.obs1) { try { that.obs1.disconnect(); that.obs2.disconnect();} catch(e){
console.log("Error during turn off of observations,",e);  } }
if (disable || ! config.monitor[website]) return;
var a,b,sels=getHidElemsCmd(),
nomonitor=set=>{ if (set==1) { that.off.push(true); a=that.obs1.takeRecords(); b=that.obs2.takeRecords();
//if(a.length ||b.length) console.log("TOOK records");
} // jquery get causes set, hence inf.loop.
if (set==0) { that.off.pop(); a=that.obs1.takeRecords(); b=that.obs2.takeRecords();
//if(a.length ||b.length) console.log("0TOOK records");
} return that.off.slice(-1)[0]; };
var parseCssText=str=>JSON.parse("{" + (str||"").replace(/[\w-]+(?=:)/g,'"$&"').replace(/:\s*(.+?)(?=;)/g,':"$1"').replace(/;/g,",").slice(0,-1) + "}");
console.info("WebEraser message: Monitoring elements that match given selectors for creation and display and to be erased on sight.");
$(sels).each((i,el)=>$(el).data("sfswe-oldval", $(el).css(["display","visibility","height","width"])) ); //copy of style obj but dead (eg, cssText not updated).
obs1_connect(sels);
function obs1_connect(selectors) {
that.obs1=attrModifiedListener(document,selectors,["style","class","id"],function(mutrecs) {
if (nomonitor()) return;
nomonitor(1);
var rec=mutrecs[0], t=rec.target, target=$(t), attr=rec.attributeName;
var oldval=target.data("sfswe-oldval"), currval=target.css(["display","visibility","height","width"]);
//console.log("Attr modified: "+attr,	"\n\nmut.oldValue--attr currvalue\n\n    ",			rec.oldValue,"\n\n ---",nodeInfo(target.attr(attr)),"\n\n\ntarget.data.oldvals:\t\t",			nodeInfo(oldval),"\n\nCurvals from .css():\t\t",nodeInfo(currval),			"\n\n\ntarget",target,"\n\nAll "+mutrecs.length+" All mutation records with oldvals:\n",			mutrecs.map(x=>"\noldval: "+x.oldValue+"\t\t\t\tnode: "+nodeInfo(x.target)).join(" ")  );
//ldval=parseCssText(mutrecs[0].oldValue);	    //var moldval=parseCssText(rec.oldValue);
var objsel=target.attr("selmatch-sfswe");
if (!objsel) {
target.data("sfswe-oldval", target.css(["display","visibility","height","width"]));
markForTheCurtains(target,t,findMatchingSelector(target,selectors));
}
if (!oldval && /class|id/.test(attr)) { //&& target.prev("sfswediv")[0]) {
var newlen=that.obs1.add(target);
oldval={};
}
if (currval.display=="none" && oldval.display!="none") {
bodymsg("change-nodisplay:"+target.attr("selmatch-sfswe"));
target.children("sfswediv").css("display","none");
measureForCurtains();
} else if (currval.display!="none" && (oldval.display=="none" || oldval.display==undefined )) {
bodymsg("change-display:"+target.attr("selmatch-sfswe"));
target.children("sfswediv").css("display","");
closeCurtains(target); //,true); //no animation since asynch anime will trigger too many mutation records.
}
if ( parseInt(currval.height)|0 - parseInt(oldval.height)|0) {
bodymsg("change-height:"+nodeInfo(target)+" "+currval.height);
measureForCurtains();
} else if ( parseInt(currval.width)|0 - parseInt(oldval.width)|0) {
bodymsg("change-width:"+nodeInfo(target)+" "+currval.width);
measureForCurtains();
} else if (currval.visibility!=oldval.visibility)
bodymsg("change-visibility:"+nodeInfo(target));
// 	if (currval.visibility=="visible")  {
// 	    bodymsg("change-display:"+target.attr("selmatch-sfswe"));
// 	    target.prev().css("display","");
// 	    closeCurtains(target,true); //no animation since asynch anime will trigger too many mutation records.
// 	} else if (currval.visible!="visible") {
target.data("sfswe-oldval",currval);
// change-visibility?
//}); //forEach
nomonitor(0);
}); // attrModifiedListener(...
} // obs1_connect()
that.obs2=nodeMutationListener(document,sels, function(foundArrayOfNodes, parentOfMutation,removed) {
if (nomonitor()) return;
nomonitor(1);
foundArrayOfNodes.forEach(node=>{   // A flattened subtree, if node was again removed quickly it may have no parent.
var jQnode=$(node);
if (!removed) { // new node inserted.
jQnode.data("sfswe-oldval", jQnode.css(["display","visibility","height","width"]));
var foundsel=findMatchingSelector(jQnode,sels);
bodymsg("new-node:"+foundsel);
markForTheCurtains(jQnode,node,foundsel);
closeCurtains(jQnode,false,measureForCurtains); //nomonitor(0); },300);
} else { // node removed
//if(jQnode.attr("cc"))  {
bodymsg("node-delete:"+jQnode.attr("selmatch-sfswe"));
$(".CurtainRod[cc='"+jQnode.attr("cc")+"']").remove(); //.filter(function(){return $(this).data()})
measureForCurtains();
//}
}
});//forEach
nomonitor(0);
},true); //nodeMutationListener()
}
function findMatchingSelector(obj,sels) {
return sels.split(/,/).find(sel=>obj.is(sel));
}
function openCurtains(zap_or_keep="",curtains=$(".WebEraserCurtain")) { // called from ctrl-click with curtains, eraseElementsCmd() w/o curtains, and lrcurtains.click sets "keep"
log("openCurtains",zap_or_keep,"curtains:",curtains);
setTimeout(function() {
curtains.each(function() { $(this).parent().css("visibility","hidden");});
manimate(curtains,["width",0,"%"],3500,8,function() {
var that=$(this), erased_el=jQuery.data(this.parentNode,"covered-el");
var sel=erased_el.attr("selmatch-sfswe");
bodymsg("opened curtains for sel:"+sel+", cc:"+erased_el.attr("cc"));
switch(zap_or_keep[0]) { // z: zap from layout, k: keep layout, t temporarily rm curtains, a: alt rm erasure
case "z": zaplists.add(sel);erased_el.css("display","none");measureForCurtains();console.info("Completely erased,",sel+".");break;
case "k": zaplists.add(sel,"keep");;erased_el[0].style.setProperty("visibility","hidden","important");console.info("Hidden for layout,",sel+".");break; //keep_layout
case "t": that.parent().css("display","none");break;           //tzap
case "a": hidElementsListCmd("rm",sel); observeThings(); that.parent().remove();markForTheCurtains(erased_el,0,0,"unmark"); break; //azap
}
//erased_el.prev().css({display:"none"});
});
},1000);
return false;
}
// Outline overview of layout:
//
// <DIV id=xyz class=Web-Eraser-ed selmatch-sfswe="DIV#xyz"> // target el, for covering.  el.children("sfswediv") has data covered-el to here.
//   <sfswediv class=CurtainRod cc=n data.coveredEl=divtarget>
// 	    <img class=webEraserCurtain sfswe-left>
// 	    <img> class=webEraserCurtain sfswe-right>
//   </sfswediv>
// </DIV>
//
function createCurtains(el, noAnimKeepLayout) {
var h=el.outerHeight()|0,w=el.outerWidth()|0, area=h*w, iw=w/2, //pos= moffset(el),
warea=window.innerHeight*window.innerWidth, csspos=el.css("position");
log("createCurtains ",noAnimKeepLayout,"h/w",h,w," el:",el);
// 9 msecs to here from function start.
var lsrc=curtain_icon.split(/\s+/)[0], rsrc=curtain_icon.split(/\s+/).slice(-1); //last string
//if (!getValue("ownImageAddr","")) switch(true) {
if(!ownImageAddr) switch(true) {
case w<250:  lsrc=rsrc=curtain_xslim_icon;break;
case w<500:  lsrc=rsrc=curtain_slim_icon;break;
case w>800: lsrc=rsrc=curtain_wide_icon;break; }
var lcurtain=$("<img class='WebEraserCurtain sfswe-left' style='left:0;position:absolute;height:100%;visibility:visible;'>");
lcurtain.attr("src",lsrc);
setTimeout(()=>{
if (lcurtain[0].complete||plat_chrome) return;
lcurtain[0].src="https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains.orig.jpg";
rcurtain[0].src="https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains.orig.jpg";
},500);
var rcurtain=$("<img class='WebEraserCurtain sfswe-right' style='right:0;position:absolute;height:100%;visibility:visible;' src="+rsrc+"></img>"),
curtainRod=$("<sfswediv tabindex=0 class=CurtainRod cc="+(++curtain_cnt)+" style='z-index:2147483640; position:absolute; display:block; opacity:0.94;visibility:hidden'></sfswediv>"),//background-color:#888; !!overflow:hidden; rm'ed //inline is default here, 'd take full width of parent.
lrcurtains=lcurtain.add(rcurtain), sel=el.attr("selmatch-sfswe");
//Absolute is relative to nearest non-statically positioned ancestor, this is returned from elem.offsetParent.
el.attr("cc",curtain_cnt);
curtainRod.append(lcurtain,rcurtain);
curtainRod[0].title="Shift-Click to hide and preserve page layout.\nCtrl-click to persistently delete from layout.\nAlt-Click to remove erasure.\nDouble click to open or close curtains.\nClick to focus and enable typing of 'w', for widen, 'n', for narrow, 'l', lighten."
+"\n\nSelector is: "+sel+", webpage is:"+webpage+".";
lrcurtains.contextmenu(e=>(eraseElementsCmd(),false));
lrcurtains.click(function({ctrlKey:ctrl,shiftKey:shift,altKey:alt,target:target}) {
var that=$(this),lrcurtains=that.add(that.siblings());
log("lrcurtains.click alt",alt,"lrcurtains:",lrcurtains,"this:",this);
if (!(alt||shift)) return;
if (ctrl&&shift) alert("Curtained target is,"+target,"lrcurtains:",lrcurtains,"this",this);
else if (shift) openCurtains("keep_layout",lrcurtains);
else if (alt) openCurtains("azap",lrcurtains);
else if (ctrl&&alt) that.parent().focus();
return false;
});
lrcurtains.dblclick(e=>openCurtains("tzap",lrcurtains));
curtainRod.dblclick(e=>openCurtains("tzap",lrcurtains));
el.dblclick(e=>closeCurtains(el)); el.mousedown(e=>false);  el.mouseup(e=>false);  el.click(e=>false);
curtainRod.keypress(moveRod);
//curtainRod.css({height:h,width:w}).css(pos).data({coveredEl:el,selmatchSfswe:sel});
//curtainRod.css({height:h,width:w}); //.css(pos);
curtainRod[0].style.setProperty("float","none","important");
curtainRod[0].style.setProperty("width",w+"px","important");
jQuery.data(curtainRod[0],"coveredEl",el);
jQuery.data(el[0],"rodEl",curtainRod);
//log("data covered-el",jQuery.data(curtainRod[0],"covered-el"),"for rod",curtainRod[0]);
//log("\nAnd rod-el is ",jQuery.data(el[0],"rod-el"),"for el",el[0]);
lrcurtains.css({ width: (!noAnimKeepLayout ? 0 : "51%" )}); // Initial width of each curtain.
var portions=area/warea*100|0;    //curtainRod.attr("init-calc",(calc|0)+" "+portions);
if (portions>=60) { //>75% of window is covered.
var visible_area;
with (Math) {visible_area=min(w,window.innerWidth)*min(h,window.innerHeight);}
if (visible_area>=warea*0.6) {
lcurtain.css({left:"10%"});
curtainRod.css({width:"80%",top:"10%"}).addClass("sfswe-overlay");
lrcurtains.css({height:h*0.8});
setTimeout(x=>$("html, body").css("overflow",(i,v) =>
v=="hidden" ? "auto": null).css("position",(i,v) => v=="fixed" ? "static": null),4000);
overlay=true;
//First event listener can stop prop to ones added later, ideally would be added at doc-start.
console.info("This element, chosen for erasure, is an Overlay (>2/3 covered, "+portions+"%, "+h+"x"+w+"): ", sel, el);}}
assertZ(el);
if(!el.is("iframe,img,canvas")) {
curtainRod.css({height:"100%",width:"100%",left:0,top:0});
el.prepend(curtainRod);        ////////////////////
curtainRod.parent().css("position","relative"); // ensure also that left and top are 0.
}
else {
curtainRod.addClass("outie");
let pos=moffset(el);
log("OUTIE pos",pos,h,w, "$pos:",el.position());
curtainRod.css({height:h,width:w}).css(pos); //	curtainRod.css({height:"100%",width:"100%",left:0,top:0});
el.wrap("<div class=sfswe-contner>");
el.before(curtainRod);
}
return [curtainRod,lrcurtains];
}
function moveRod(e) {
if (e.key=="w"||e.key=="n") {
let  newel, rod=$(this), el=jQuery.data(this,"covered-el")||$(), p=el.parent(), newsel, oldsel=el.attr("selmatch-sfswe");
var trace=el.find(":data(pewiden-trace):first");
if(trace.length==0) trace=el.find(">:only-child");
if (e.key=="n")   newel=trace; //narrow
else              newel=p;     // widen
if (newel.length==0 || newel.is("body")) {	rod.focus();$("body").blur();rod.focus(); return false;}
newsel=selector(newel,0,false,0,"Web-Eraser-ed");
el.data("pewiden-trace","true");
hidElementsListCmd("mv", oldsel, newsel);
newel.before(rod);
jQuery.data(this,"covererEl",newel);
markForTheCurtains(el,null,null,"unmark");
markForTheCurtains(newel,newel[0],newsel);
rod[0].title=rod[0].title.replace(/\nSelector is:.*\./,"\nSelector is:"+newsel+".");
measureForCurtains();
rod.focus();
} else if (e.key=="l") { //lighten
var rod=$(this);
var op=rod.css("opacity");
rod.css("opacity",op*0.8);
setTimeout(x=>rod.css("opacity",rod.css("opacity")*1.25),10000);
} else if (e.key=="s") {
var sfsprelog=$("#sfswe-div-logger");
sfsprelog.css("display","");
sfsprelog[0].scrollIntoView();
}
return false;
}
function toggleCurtains() {
var that=arguments.callee;
$(".CurtainRod").each(function(){
if (!that.xor)    {manimate($(".WebEraserCurtain",this),["width",51,"%"],2000,12);}
else              manimate($(".WebEraserCurtain",this),["width", $(this).data("init-width"),"%"],4000,8);
});
}
function zaplist_composite() { // composite pattern.  4 objs.  Those on zaplist are for complete erasure, but may keep layout.
if (iframe) return;
var zlists=[new zaplist(webpage),new zaplist(website),new zaplist(webpage,"kl"),new zaplist(website,"kl")];
this.add=function(sel,keep_layout){
zlists.forEach(function(el) { el.add(sel,keep_layout);});   };
this.contains=function(el){  // may be a dom/jq object or a string selector.
return zlists.some(function(list) { return list.contains(el);}); };
this.which=function(el) { // The 2 bits returned tell if & on which zaplist the elem is.
if (this.contains(el)) {
var has_keep_layout=zlists.map(v => v.contains(el)).includes("kl");
return {keep_layout:has_keep_layout,zap:!has_keep_layout};
}
return {keep_layout:false,zap:false};
};
this.update=function(sel){	zlists.forEach(function(el) { el.update();});  };
this.toString=()=>"[object zaplist_composite]";
}
function zaplist(key,keytype) {
var fullkey=key+":zaplist"+(keytype? ":"+keytype : "");
var savelist=function() { var p=setValue(fullkey,list);
if (!list.length && GM.deleteValue) p=GM.deleteValue(fullkey);
//log("saved zaplist fullkey",fullkey,", list:",list,"getval");
return p;
};
var readlist=function() { return getValue(fullkey,[]); };
var list;   //console.log("zap inited:",key,keytype);
this.add=async function(str,kl) {
log("zaplist add, str:",str,"in kl:",kl,"this.fullkey:",fullkey);
if (!!kl != !!keytype) return;
list.push(str);
if((await getValue(key+":erasedElems","")).split(/,/).includes(str)) {
await savelist();
} else { log("pop goes the attempt");list.pop();}
};
this.contains=function(jqobjOrStr){
//log("zap check if list contains obj:",jqobjOrStr,"within its \nlist:",list,"FUll key",fullkey);
if (list.length==0) return;
if (jqobjOrStr.attr) jqobjOrStr=jqobjOrStr.attr("selmatch-sfswe");
if (list.indexOf(jqobjOrStr) != -1)
return keytype||"zap";
};
this.rm=function(str) {
var i=list.indexOf(str);
if (i!=-1)   list.splice(i,1);
savelist();
};
this.update=async function() { // If sels removed from main list also remove from zaplists.
if(!list) list=await readlist();
//log("zaplist.update key:",fullkey,"read list:",list);
//if (list.length==0) return;
var strs_ar=getHidElemsCmd().split(/,/);
list=list.filter(function (lel) {
return strs_ar.includes(lel);
});
savelist();
};
this.toString=()=>"[object zaplist]";
}
function moffset(elem, eld=elem[0]) { try{
if (  elem.find("*").addBack().filter(function(){
return $(this).css("position").includes("fixed");
}).length )	                                               //    if (elem.css("position").includes("fixed"))
return Object.assign(elem.position(),{position:"fixed"});
var dominPar=elem.offsetParent()[0]; // gets closest element that is positioned (ie, non static);
return left_top(elem);
function left_top(elem) {
var {left,top}= elem.position(); // something sets & unset margintop or left during something here for some reason, margins and floating els may disaffect calc!
let margl=parseInt(elem.css('margin-left')), margt=parseInt(elem.css('margin-top'));
//let bordl=parseInt(elem.css('border-left-width')), bordt=parseInt(elem.css('border-top-width'));
var x = left + margl, y = top + margt;
do {
elem = elem.offsetParent();
if (elem.is(dominPar) || elem.is("html")) break;
let {left,top}=elem.position(); // something sets & unset margintop during something here for some reason, margins and floating els may disaffect calc!
x += left; y += top;
} while (true)
if (y) y--;
return { left: x, top: y };
}
}    catch(e){ console.log("Error moffset",e); }}
function assertZ(el){
var dominPar=el.offsetParent();
//	var tnames=["transform","-webkit-transform","-webkit-perspective"];
var tnames=["transform","perspective"];             // jquery adds vendor suffixes, eg -webkit-
el.parentsUntil(dominPar).addBack().each(function(){
var that=$(this), tforms=that.css(tnames),tf={};
//log("assertZ dominpar:",dominPar,"tforms:",tforms);
if(Object.values(tforms).some(x=>!/none/.test(x))) {
tnames.forEach(name=>tf[name]="none");
that.css(tf);that.addClass("assertedZ");
}
//log("assertz changed css",tforms,"\n",tforms,"now it is: ",that.css(tnames));
});
}
//
// MutationObserver functions.           Eg, var obs=nodeInsertedListener(document,"#results", myCBfunc);  function myCBfunc(foundArrayOfNodes, DOMparentOfMutation);
// Requires jQuery.
// See https://www.w3.org/TR/dom/#mutationrecord for details of the object sent to the callback for each change.
// Four functions available here:
// Parameter, include_subnodes is to check when .innerHTML add subnodes that do not get included in normal mutation lists, these lower nodes are checked when parameeter is true.
// Return false from callback to ditch out.
function nodeInsertedListener(target, selector, callback, include_subnodes) {
return nodeMutation(target,selector,callback,1, include_subnodes);
}
function nodeRemovedListener(target, selector, callback, include_subnodes) {
return nodeMutation(target,selector,callback,2, include_subnodes);
}
function nodeMutationListener(target, selector, callback, include_subnodes) { //inserted or removed, callback's 3rd parameter is true if nodes were removed.
return nodeMutation(target,selector,callback,3, include_subnodes);
}
function attrModifiedListener(target, selectors, attr, callback) { //attr is array or is not set.  Callback always has same target in each mutrec.
var attr_obs=new MutationObserver(attrObserver), jQcollection=$(selectors);
var config={ subtree:true, attributes:true, attributeOldValue:true};
if (attr) config.attributeFilter=attr;      // an array of attribute names.
attr_obs.observe(target, config);
function attrObserver(mutations) {
var results=mutations.filter(v=>{ return $(v.target).is(selectors)||$(v.target).is(jQcollection);});
if (results.length) { //Only send mutrecs together if they have the same target and attributeName.
let pos=0;
results.reduce((prev_res,curr,i)=>{ if ( prev_res.target!=curr.target || prev_res.attributeName != curr.attributeName) {
callback(results.slice(pos,i)); pos=i;  } // not really a reduce!
return curr;
});
callback(results.slice(pos)); //////////////////<<<<<<<
} }
attr_obs.add=function(newmem) { jQcollection=jQcollection.add(newmem); return jQcollection.length; };
return attr_obs;
}
//
// Internal functions:
function nodeMutation(target, selectors, callback, type, include_subnodes) { //type new ones, 1, removed, 2 or both, 3.
var node_obs=new MutationObserver(mutantNodesObserver);
var jQcollection, cnt=0;
node_obs.observe(target, { subtree: true, childList: true } );
return node_obs;
function mutantNodesObserver(mutations) {
var sel_find, muts, node;
jQcollection=$(selectors);
for(var i=0; i<mutations.length; i++) {
if (type!=2) testNodes(mutations[i].addedNodes, mutations[i].target); // target is node whose children changed
if (type!=1) testNodes(mutations[i].removedNodes, mutations[i].target,"rmed"); // no longer in DOM.
}
function testNodes(nodes, ancestor, rmed) { //non jQ use, document.querySelectorAll()
if (nodes.length==0) return;
var results=[], subresults=$();
for (var j=0,node; node=nodes[j], j<nodes.length;j++) {
if (node.nodeType!=1) continue;
if (jQcollection.is(node)) results.push(node);
if (include_subnodes) subresults=subresults.add($(node).find(jQcollection));
}
results=results.concat(subresults.toArray());
if (results.length) callback(results, ancestor, rmed);
} //testNodes()
};
}
//
// End MutationObserver functions.  Usage example, var obs=nodeInsertedListener(document,"div.results", myCBfunc);  function myCBfunc(foundArrayOfNodes, ancestorOfMutation);
//
function manimate(objs,[css_attr,target_val,suffix,delay],interval,noOf_subintervals,CB) { // CB is invoked once, at end.  $.animate max-ed out cpu for 30 secs or so.
var len=objs.length,cnt=0,i,random_element=3;
if (!len) return false;
var maxi=objs.length-1, subinterval=interval/noOf_subintervals,
init_int=parseInt(objs[0].style[css_attr]), // assume same initital position and same units/suffix for all objs.
m=(target_val-init_int)/noOf_subintervals,
linear=(v,i)=>init_int+m*(i+1),	// quad=(v,i)=>Math.min(target_val_int,init_int+(5/3)*Math.pow(i+1,2)-(5/3)*(i+1)),	// combo=(v,i)=>quad(v,i)/2+linear(v,i)/2,
plotvals=new Uint32Array(noOf_subintervals).map(linear);
//console.log("manimate() targets:",objs," requestAnimationFrame:",css_attr,"currval:",objs.css(css_attr),target_val,interval,noOf_subintervals,CB,objs,"plotvals:",plotvals);
subinterval+=random(-subinterval/random_element,subinterval/random_element);  /// Random element +/- 1/random_element.
if (delay) setTimeout(x=>i=setInterval(eppursimuove,subinterval,objs),delay);
else i=setInterval(eppursimuove,subinterval,objs);
function eppursimuove(that,b,c) {try{
requestAnimationFrame(tstamp=>objs.css(css_attr,plotvals[cnt]+suffix));
if (++cnt==noOf_subintervals) {
clearInterval(i);
CB && CB.call(that[1]);
}
} catch(e){log("WebEraser eppursimuove(), error@",Elineno(e),e);}}
} //manimate()
async function regcmds(){
var reg_args;
if(!regcmds.done) {
reg_args=["Erase Web Elements ["+(elems_to_be_hid?"some erased":"none erased")+"]", eraseElementsCmd,"","", "E"];
if(!GM_registerMenuCommand(...reg_args))  // from GM4_registerMenuCommand_Submenu_JS_Module, if there, undefined, else from gm4-polyfill which returns the menuitem DOM object.
GM.registerMenuCommand(...reg_args);  // from gm4-polyfill.  It sets body contextmenu style menu.
var ctrl_e_ON=await getValue("eraseElems_ctrlE",false);
if(ctrl_e_ON) $(window).keypress(function(e) {
if (!e.ctrlKey || e.key!="e") return;
setTimeout(function(){salert("Invoking web erasure function.");},300);
inner_eraseElements("fromCtrlE");
});
reg_args=["Set ctrl-e to invoke WebEraser now"+(ctrl_e_ON ? "[on]" : "[off]"), function(){
ctrl_e_ON^=true;
setValue("eraseElems_ctrlE",ctrl_e_ON);
alert("Ctrl-e function is now "+(ctrl_e_ON?"enabled":"disabled")+".  When pages load the erase function is invoked.  However, if the webpage is unusual "
+"it may delay this erasure, ctrl-E can be typed to invoke the erasure at any time when "
+"ctrl-e function is enabled.  Select again from menu to toggle");
},"","", ""];
if(!GM_registerMenuCommand(...reg_args))
GM.registerMenuCommand(...reg_args);
regcmds.done=1;
}// endif ! regcmds.done
if (regcmds.done==2 || $(".Web-Eraser-ed").length == 0) return;
reg_args=["Clear All WebErasures here on page & site; reloads page.",async function(){
await deleteValue(website+":erasedElems");
await deleteValue(webpage+":erasedElems");
location=location;
}];
if(!GM_registerMenuCommand(...reg_args))
GM.registerMenuCommand(...reg_args);
regcmds.done=2;
} // regcmds()
function setValue(n,v) {
if (!v && GM.deleteValue) return GM.deleteValue(n);
else return GM.setValue(n,JSON.stringify(v));
}
async function getValue(n,v) { var r1,res=await GM.getValue(n,JSON.stringify(v)); try {
r1=JSON.parse(res); return r1; } catch(e) { console.log("Error in parse of res:"+res+".Value:"+v+".  Error:",e); return v; } }
function random(min,max) {
return Math.floor(Math.random() * ((max+1) - min)) + min;
}
function timer() { //console.time() and console.timeEnd() not working at the mo, so tstamp sent with each console.log
// Use with eg, time("start"); ... time("end"); // Each call to time()  for from start and from previous call to time().
//if (window!=window.parent || timer.log) return;
if (timer.log) return; // aleady started
var originalLogger = console.log;
timer.log=originalLogger;
console.log = function () {
if (!timer.begin) {
timer.begin=Date.now();
timer.last_time=timer.begin;
originalLogger.call(timer.begin,">>>>Init timer "+location.pathname+":");
}
var args=Array.from(arguments);
var tstamp=Date.now();
var sdiff=tstamp-timer.begin, ldiff=tstamp-timer.last_time;
args.unshift(sdiff+"ms, "+ldiff+"ms\t");
timer.last_time=tstamp;
originalLogger.apply(this, args);
};
}
function logger2() {
var originalLogger = console.log;
logger2.log=originalLogger;
console.log = function () {
//alert(Array.from(arguments));
var roll="";
for (var i=0;i<arguments.length;i++)
roll+=arguments[i]+" ";
document.body.innerHTML+=roll+"<br>";
//originalLogger.apply(this, arguments);
};
// var originalInfo = console.info;
// logger2.info=originalInfo;
// console.info = function () {
// 	//alert(Array.from(arguments));
// 	document.body.innerHTML+=Array.from(arguments);
// 	originalInfo.apply(this, arguments);
// };
// var originalError = console.error;
// logger2.error=originalError;
// console.error = function () {
// 	//alert(Array.from(arguments));
// 	document.body.innerHTML+=Array.from(arguments);
// 	originalError.apply(this, arguments);
// };
}
function logger() {
$(document).dblclick(outputlogger);
var originalLogger = console.log;
logger.log=originalLogger;
console.log = function () {
if (!logger.this) logger.this=this;
// Do your custom logging logic
var argq=$(document).data("loggerq");
var args=Array.from(arguments);
if (!argq) argq=[];
if (document.readyState!=logger.state) {
argq.push(document.readyState+":");
logger.state=document.readyState;
}
argq.push(args);
$(document).data("loggerq",argq);
args.push(document.readyState);
originalLogger.apply(this, args);
};
} //logger()
function csscmp(prevval, newval) {try{
var that=arguments.callee;
var covered={}, roll="";
for (let i in prevval) {
covered[i]=1;
if (newval[i]===undefined) roll+="Removed: "+i+"="+prevval[i]+" ";
else if (prevval[i]!=newval[i]) roll+="Changed: "+prevval[i]+" to: "+newval[i]+" ";
}
for (let i in newval) if (!covered[i]) roll+="Added: "+i+"="+newval[i]+" ";
return roll||"Same";
}catch(e) {console.error("csscmp Error",e.lineNumber,e);}}
function nodeInfo(node1,plevel,...nodes) { // show DOM node info or if name/value object list name=value
//console.log("nodeInfo stack:",logStack());
if (node1==undefined || node1.length==0) return;
plevel=plevel||1;
if (isNaN(plevel) && plevel) { nodes.unshift(node1,plevel); plevel=1; }
else nodes.unshift(node1);
plevel--;
return nodes.map(node=> {
if (!node || typeof node=="string") return node;
if (node && node.attr) node=node[0];
if (node && node.appendChild) {
let classn=node.className ? node.className.replace("Web-Eraser-ed","") : "";
return node ? node.tagName.toLowerCase() + classn.replace(/^\b|\s+(?=\w+)/gi, ".").trim() + (node.id||"").replace(/^\s*\b\s*/,"#")
+ (plevel>0 ? "<" + nodeInfo(node.parentNode,plevel):"")
: "<empty>";
}
else if (node && node.cssText) return node.cssText;
else
return ""+Object.entries(node)      // entries => array of 2 member arrays [[member name,value]...]
.filter(x=> isNaN(x[0]) && x[1] )  //Only name value members of object converted to string.
.map(x=>x[0]+":"+x[1]).join(", ");
}).join(" ");
}
//selector(node,node.parentNode,0,0,"Web-Eraser-ed").replace(/^html>body>/,""); }
function outputlogger() {
var originalLogger=logger.log;
var that=logger.this;
var argq=$(document).data("loggerq");
originalLogger.call(that,"===============Logger Output==========================");
argq.forEach(function(v){
originalLogger.call(that,v); //this changes in forEach in this case!
}); // originalLogger.apply(this,argq);
originalLogger.call(that,"===============End Logger Output=======================");
return false;
};
function logStack(fileToo) { // deepest first.
var res="", e=new Error;
var s=e.stack.split("\n");
if (fileToo) res="Stack of callers:\n\t\t"; //+s[1].split("@")[0]+"():\n\t\t"
for (var i=1;i<s.length-1;i++)
res+=s[i].split("@")[0]+"() "+s[i].split(":").slice(-2)+"\n";
return !fileToo ? res : {Stack:s[0]+"\n"+res};
}
function Ppositions(el, incl_self,not_pos_break="") {
el=$(el); var roll="\n\n";
var els=el.parents();
if (incl_self) els=els.add(el).reverse();
els.each(function(){
var pos=$(this).css("position");
roll+=this.tagName+" "+pos+"\n";
if (! pos.includes(not_pos_break)) return false;
//       /^((?!relative).)*$/   matches any string, or line w/o \n, not containing the str "relative"
});
return roll;
}
function ttimer(stage) {
return; //!! for profiling only.
if(window==window.parent) {
console.time("----from "+stage);
if(ttimer.last_stage) console.timeEnd(ttimer.last_stage); // print to console: "tTimer[n]: [num]ms"
if(ttimer.last_stage) console.log("\t----to "+stage);
ttimer.last_stage="----from "+stage;
}
}
function escapeCatch(cbfunc,perm) { // Usage: call first time to install listener & add a callback for keydown of escape key.  Optionally then call many times adding callback functions.
var that=escapeCatch;
if(!that.flist || that.flist.length==0) {
that.flist=[cbfunc];
window.addEventListener("keydown", subfunc, true);
function subfunc(e) {
if (e.which == 27)  {
console.log("escape",perm,"this is:",this);
that.flist.forEach(func=>func());
if(perm) return;
window.removeEventListener("keydown", subfunc, true);
that.flist=[];
}
};
} else
if(cbfunc) { that.flist.push(cbfunc); return; }
}
function summarize(longstr, max=160)  {
longstr=longstr.toString();
if (longstr.length<=max) return longstr;
max=(max-3)/2;
var begin=longstr.substr(0,max);
var end=longstr.substr(longstr.length-max,max);
return begin+" ...●●●●... "+end;
}
function jqueryui_dialog_css() {
return ".ui-dialog-content,.ui-dialog,.ui-dialog textarea { font-size: 12px; font-family: Arial,Helvetica,sans-serif; border: 1px solid #757575; "
+"background:whitesmoke; color:#335; padding:12px;margin:5px;} "
+".ui-dialog-buttonpane {  width:94%; background:whitesmoke; font-size: 10px; cursor:move; border: 1px solid #ddd; overflow:hidden; } "
+".ui-dialog-buttonpane button { background: #f0f0e0; }"
+".ui-dialog-buttonset { float:right; } "
+".ui-widget-overlay { background: #aaaaaa none repeat scroll 0 0; opacity: 0.3;height: 100%; left: 0;position: fixed;  top: 0; width: 100%;}"
+".ui-button,.ui-widget-content { text-align:left; color:#333; border: solid 1px #757575; padding: 6px 13px;margin: 4px 3px 4px 0;} "
+".ui-corner-all,.ui-dialog-buttonpane {border-bottom-left-radius:30px;}"
+".ui-button:hover { background-color: #ededed;color:#333; } "
+".ui-button { background-color: #f6f6f6; color:#333; }"
+".ui-dialog {position:absolute;padding:3px;outline:none;}"
+".ui-resizable-handle { position:absolute; cursor: url(data:image/svg+xml;base64,"
+"iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAAAAACo4kLRAAAACXBIWXMAAAsTAAALEwEAmpwYAAABAUlEQVQY022RsWrCYACEvz9EK1HQCilYu+jWdNKlj9BNN1/Cp/IBFHTMExihdTCIm0sR26W6KNHkvw6lunjLjffdnXn7fG0/PzzeG0A/m+/Ve/REB3CL/laStn7RBTpOG0iTXgWg0ktSoO20DJDv5gBy3TxgWkQFAG+QSdnAAyhErMtuFSiN0nRUAqpuec2x2V/4YOr7fd2AH/ebR+zhbMOaCWJrF4GphfZ8sEhSNm3EVrJxY5pJkhGAkjtzNRxuyAGws2Ap0DKYWYDbQRek3e6K9A8/TNPhBf5mzbEBvDCTpCz0ADN25gJOkzPAeXICNL85spu8/N0B8LDafK0+ouQXfemVYVtdIewAAAAASUVORK5CYII="
+") 10 10, row-resize; } .ui-resizable-sw {bottom:5px;left:5px;}"
+".ui-resizable-w, .ui-resizable-e { width:10px;height:100%;top:-5px;} .ui-resizable-n, .ui-resizable-s { width:100%;height:10px;} .ui-resizable-n {top:-5px; } .ui-resizable-w {left:-5px; } .ui-resizable-e {right:-5px; }"
+ (str=>str+str.replace(/-moz-/g,"-webkit-"))(
//	    ".sfswe-content :-moz-any(div,input) { font-size:13px;padding:6px;margin:4px 3px 4px 0;color:#333; opacity:1;  }"
".sfswe-content :-moz-any(div,input) { font-size:13px;padding:0px;margin: 0;color:#333; opacity:1;  }" //background:whitesmoke;
+".sfswe-content :-moz-any(span) { font-size:13px;padding:0;margin:0;color:#333;}"
+".sfswe-content :-moz-any(a,a:visited)    { color:#333;text-decoration:underline; padding:0;margin:0;}"
+".sfswe-content :-webkit-any(a,a:visited) { color:#333;text-decoration:underline; padding:0;margin:0;}"
)  +".sfswe-content a:hover {opacity:0.5;}"
+".ui-tooltip { font-size: 7px; }"
+".sfswe-ticks * {font-size:11px;padding:0px;margin:2px;}"
.replace(/\.ui/g,".sfswe-sprompt .ui"); //gives namespace of .sfswe-prompt
}
function environInit(environ) { // returns false if GM environment is there, otherwise it calls main when ready and immediately returns true.
environ.plat_chrome=false; environ.plat_msedge=false;        //chrome standalone, ie, not under tamper in chrome.	// environ.plat_msedge=/Edge[\d./]+$/i.test(navigator.userAgent);
if (/Chrome/.test(navigator.userAgent)) environ.plat_chrome=true;
environ.plat_mac = /^Mac/.test(navigator.platform);
try { environ.nonGMmode= (typeof GM == "undefined"); } // || "Barychelidae"!=GM_getValue("arachnoidal","Barychelidae"); }
catch(e) { environ.nonGMmode=true; }; //eg, chromium stadalone
//environ.nonjQ = !window.jQuery || parseFloat(jQuery.fn.jquery) < 3.1;
if (nonGMmode){ // chromium bare, ie, w/o tamper.
console.info("WebEraser userscript in non GM_ mode at "+location.href, "typeof GM:",typeof GM, "nonGMmode",nonGMmode);
environ.unsafeWindow=window;
environ.old_GM_getValue=environ.GM_getValue;
try { localStorage["anothervariable"]=32; }	catch(e) {
window.nostorage=true;
if(!iframe) console.error("No local storage, no GM storage, use Tampermonkey to include this script on page:",location.href);
window.localStorage={};
}
//if(!window.nostorage) console.log("Have local storage",localStorage.anothervariable);
environ.GM_getValue=function(a,b) { return localStorage[a]||b; };
environ.GM_setValue=function(a,b) { localStorage[a]=b; };
environ.GM_getResourceURL=function(url) {
var ext="Dbl"; if (url.endsWith("Orig")) ext=".orig"; else if (url.endsWith("Xsm")) ext="ExSm"; else if (url.endsWith("Trpl")) ext="Trpl";
return "https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains"+ ext +".jpg";
};
//environ.GM_registerMenuCommand=x=>null;
environ.GM_addStyle=function(cssSheet) { $("head").append("<style>"+cssSheet+"</style>"); };
environ.uneval=function(x) { return "("+JSON.stringify(x)+")";  }; //Diff is that uneval brackets string and json excludes code only data allowed in json.
var xhr_queue=[], xhr=new XMLHttpRequest();
xhr.onload=x=> { //arrow function means this remains window not xhr (as a function would).
//console.log(xhr.responseURL,"onload to eval in window, jQuery in window? ",!!window.jQuery,!!window.$,!!this.jQuery);
var synop=(xhr.response||"").substr(0,40);
try {
eval.call(window,xhr.response); } catch(e) {  console.error("Can't eval Error:"+e,".  Response:",xhr.response?xhr.response.substr(0,60)+"[60chars]":"No response text",x,xhr,", Queue:",xhr_queue); }
if (xhr_queue.length) {  xhr.open('GET', xhr_queue.shift()); xhr.send(); }
else if (!iframe) main.call(window); //////////////////
};
xhr.onerror=e=> {
console.log("W/e XHR Error: "+e,", E:",e,"XHR:",xhr,"After error queue:",xhr_queue);
if (xhr_queue.length) xhr.open('GET', xhr_queue.shift()); xhr.send();
};
var jq_versions_prior={
core: parseFloat(environ.$ && environ.$.fn && environ.$.fn.jquery) || 0,
ui: parseFloat(environ.$ && environ.$.ui && environ.$.ui.version) || 0
};
if(jq_versions_prior.core < 1.7)
xhr_queue.push("https://code.jquery.com/jquery-1.7.2.js");
if(jq_versions_prior.ui < 1.12)
xhr_queue.push("https://code.jquery.com/ui/1.12.1/jquery-ui.js");
xhr_queue.push("https://raw.githubusercontent.com/SloaneFox/code/master/gm4-polyfill-1.0.1.js");
xhr_queue.push("https://raw.githubusercontent.com/SloaneFox/code/master/gm-popup-menus-1.3.7.js");
xhr_queue.push("https://raw.githubusercontent.com/SloaneFox/code/master/sfs-utils-0.1.5.js");
xhr.open('GET', xhr_queue.shift()); xhr.send();
return true;
} else return false;              //if (nonGM || nonJQ)
}
ttimer("end globs setup");