WebEraser

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

目前为 2018-12-09 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name WebEraser
  3. // @version 1.4.8
  4. // @namespace sfswe
  5. // @description Erase parts of any webpage --annoyances, logos, ads, images, etc., permanently with just, Ctrl + Left-Click.
  6. // @license GPL-3.0-only
  7. // @copyright 2018, slow! (https://openuserjs.org/users/slow!)
  8. // @include *
  9. // @require https://code.jquery.com/jquery-3.2.1.js
  10. // @require https://code.jquery.com/ui/1.12.1/jquery-ui.js
  11. // @require https://greasyfork.org/scripts/375359-gm4-polyfill-1-0-1/code/gm4-polyfill-101.js
  12. // @require https://greasyfork.org/scripts/375360-sfs-utils-0-1-5/code/sfs-utils-015.js
  13. // @resource whiteCurtains https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtainsDbl.jpg
  14. // @resource whiteCurtainsOrig https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains.orig.jpg
  15. // @resource whiteCurtainsXsm https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtainsExSm.jpg
  16. // @resource whiteCurtainsTrpl https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtainsTrpl.jpg
  17. // @icon https://raw.githubusercontent.com/SloaneFox/imgstore/master/WebEraserIcon.gif
  18. // @run-at document-start
  19. // @author Sloane Fox
  20. // @grant GM.registerMenuCommand
  21. // @grant GM.getValue
  22. // @grant GM.setValue
  23. // @grant GM.deleteValue
  24. // @grant GM.listValues
  25. // @grant GM.addStyle
  26. // @grant GM.getResourceText
  27. // @grant GM.getResourceUrl
  28. // @grant GM_registerMenuCommand
  29. // @grant GM_getValue
  30. // @grant GM_setValue
  31. // @grant GM_deleteValue
  32. // @grant GM_listValues
  33. // @grant GM_addStyle
  34. // @grant GM_getResourceText
  35. // @grant GM_getResourceURL
  36. // ==/UserScript==
  37.  
  38. //
  39. // History
  40. // updated Nov 2018 v1.4.8 Images and Canvas elements lost functionality, returned.
  41. // 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.
  42. // updated Jan 2018 v1.3.9 Update for yet to come GM4 and already past backward compatibile GM4 polyfill. And for Chromium.
  43. // 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.
  44. // 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.
  45. // updated Aug 2017 v1.3.6 Issue with iframe when injected code not called for it due to its late creation same origin.
  46. // 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.
  47. // Safari on windows cant run userscripts.
  48. // 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.
  49. // updated Dec 2016 v1.3.0 Bug fixes, check for duplicate selectors, color & other ui issues. Removed zoomer (it used up a little cpu).
  50. // updated Nov 2016. v1.2.3 Iframe handling for deep iframes.
  51. // updated Oct 2016. v1.2.2 Fixed bug, GM menu on Chrome not closing.
  52. // v1.2.1 Adapted for use also in Google Chrome/Chromium web browser.
  53. // updated Sept 2016. v1.2 Added user option to turn on the monitoring for new nodes (node mutations).
  54.  
  55. ttimer("start");
  56.  
  57. // Globals:
  58. 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.
  59. if (typeof jQuery!="undefined") jq_saved=jQuery;
  60. var iframe=window!=window.parent, border_width=6;
  61. var win=window,
  62. host=window.document.location.host,
  63. pathname=window.document.location.pathname, webpage=host+pathname, website=host;
  64. var askedAlready,last_one_deleted, delcnt=0, gelem, gelems, gpre_elem,
  65. bblinker, promptOpen, rbcl="sfswe-redborder", pbcl="sfswe-prevborder", tbcl="sfswe-transparentborder";
  66. var tab="&emsp;&emsp;&emsp; &emsp; "; // tab=5spaces, emsp=4spaces, but HTML tab in a <pre> wider hence extra emsp's.
  67. //
  68. // Globs to be initialized asynchronously, see below, init_globs().
  69. //
  70. var page_erasedElems,site_erasedElems,curtain_icon, elems_to_be_hid,curtain_slim_icon,curtain_xslim_icon,
  71. curtain_wide_icon, config, ownImageAddr, whitecurtains, whitecurtainsoriginal, whitecurtainstriple;
  72. var ignoreIdsDupped, curtain_cnt=0;
  73. var zaplists,overlay=false;
  74.  
  75. if (iframe) {
  76. installEventHandlers();
  77. return;
  78. }
  79.  
  80. //if (!environInit()) if (!plat_msedge) $(main.bind(environ)); // In a normal GM environment, main will be called at docready.
  81. var str=GM_registerMenuCommand.toString();
  82. //for (var i=0;i<str.length;i++) console.log(str[i]);
  83. if (!environInit(environ)) if (!plat_msedge) {
  84. if(/^complete/.test(document.readyState)) main();
  85. else document.addEventListener("DOMContentLoaded",main.bind(environ)); //main(); addEventListener("load",main.bind(environ)) ; // In a normal GM environment, main will be called at docready.
  86. }
  87.  
  88. 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);
  89. Number.prototype.inRange=function(min,max){ if (this >=min && this<=max) return true;}; // Ditto.
  90. Number.prototype.withinRangeOf=function(range,target){ return this.inRange(target-range,target+range); }; // Ditto.
  91. String.prototype.prefix=function(pfix) { return this.length ? pfix+this : ""+this; };
  92.  
  93. async function main() {try{
  94. log=x=>null; //logger on/off
  95. ttimer("start of main, state: "+document.readyState);
  96. log("w/e main GM:",GM, "readyState",document.readyState,"body:",document.body,"iframe",iframe,"jQuery:",window.jQuery&&window.jQuery.fn.jquery,"$",window.$);
  97. if (!this.chrome) this.chrome=chromert;
  98. await init_globs();
  99. installEventHandlers();
  100. ensure_jquery_extended(); // may get clobbered by other script loading jQ.
  101. inner_eraseElements("init");
  102. var nerased=$(".Web-Eraser-ed").length, delay=5000+300*(2+nerased), forErasure=getHidElemsCmd("count");
  103. setTimeout(x=> {
  104. //ttimer("start of delay phase ");
  105. log("End of",delay,"delay, checking for inner_eraseElements",page_erasedElems,"or",site_erasedElems);
  106. if(page_erasedElems || site_erasedElems) inner_eraseElements("delay");
  107. else if ($(".Web-Eraser-ed").length==0 && elems_to_be_hid)
  108. console.info("WebEraser message: no match for any selectors:",getHidElemsCmd(),"\nWebpage:",webpage);
  109. var nerased2=$(".Web-Eraser-ed").length;
  110. nerased=nerased2;
  111. installEventHandlers("phase2");
  112. regcmds();
  113. //ttimer("end delay phase ",document.readyState);
  114. },delay);
  115. //!!
  116. // $(window).focus(x=>{
  117. // //$(window).off("focus");
  118. // setTimeout(x=> { //try{
  119. // forErasure=getHidElemsCmd("count");
  120. // var sels=getHidElemsCmd(), nerased=$(".Web-Eraser-ed").length;
  121. // log("WE focus, check for erasure", nerased ,forErasure,"site_erasedElems:",site_erasedElems);
  122. // if (nerased < forErasure) { inner_eraseElements("focus"); }
  123. // //}catch(e){console.error("WebEraser main--focus, error@",e.lineNumber,e);}
  124. // },400);
  125. // });
  126. GM_addStyle( jqueryui_dialog_css() //GM_getResourceText ("jqueryUiCss")
  127. +" .sfswe-prevborder { border-color:transparent !important;border-width:"+border_width+"px !important;border-style:double !important; } "
  128. +".sfswe-transparentborder { border-color:transparent !important;border-width:"+border_width+"px !important;border-style:double !important; } "
  129. +".sfswe-redborder { border-color:red !important; border-width:"+border_width+"px !important;border-style:double !important; } "
  130. +"img.WebEraserCurtain { display: block !important; color:#fff !important; }"
  131. +`.CurtainRod {
  132. background-color: #bbb;
  133. 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%);
  134. background-size: 13px, 19px, 17px, 15px;
  135. }`
  136. +".ui-dialog-buttonpane button {color:black !important;}"
  137. +'img[src*="blob:"] { display:block !important; }'
  138. ); // A later defined rule has precedence when both rules in effect.
  139. //setTimeout(inner_eraseElements,1500);
  140. if (plat_chrome && typeof submenuModule != "undefined") submenuModule.register("WebEraser"); //,"w");
  141. regcmds();
  142. setTimeout(reattachTornCurtains,4000);
  143. gelems=$();
  144. ttimer("end of main, state: "+document.readyState);
  145. } catch(e){console.log("WebEraser main(), error@",e.lineNumber,e);}} //main()
  146.  
  147. function handleClick(e,iframe_click) { try { //called from event handler in page & iframe, and pseudo called from click within iframe.
  148. if (!e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
  149. log("CLICK jwin is ","page:",location.href,"iframe?",iframe);
  150. halter();
  151. var permrm, target=e.target, frameEl=target.ownerDocument.defaultView.frameElement;
  152. if(frameEl) target=frameEl;
  153. prevDef(e);
  154. if (!iframe_click) { //not pseudo call
  155. var seltext_len=window.getSelection().toString().length;
  156. window.status="webEraser, Ctrl-Click, on HTML element:"+target.tagName+" "+seltext_len+", ifame: "+iframe;
  157. if (seltext_len != 0) { unhalt(); return; }
  158. if (target.blur) target.blur();
  159. if (iframe) {
  160. window.parent.postMessage( { type:"sfswe-iframe-click", code:0, src:location.toString() },"*"); // msg,origin makes pseudo call back here.
  161. log("post to parent from window."); //"frameElement:",window.frameElement);
  162. unhalt();
  163. return false;
  164. }
  165. } // endif !iframe_click
  166. while (/HTMLUnknownElement/.test(target.toString())) target=target.parentNode; //Avoid non HTML tags.
  167. log("Click reached target",target);
  168. if ($(target).is(".WebEraserCurtain"))
  169. if (e.button==0) {
  170. let reply=confirm("This will completely erase selected item, continue? \nOn any revisit to webpage you can check in the console for such erasures.");
  171. if (reply) openCurtains("zap",$(target).siblings("img").addBack());
  172. } else
  173. eraseElementsCmd();
  174. else if (!askedAlready) {
  175. if ($("body").is(target)) {
  176. 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."))
  177. eraseElementsCmd();
  178. unhalt();
  179. return;
  180. }
  181. halter.dialog=true;
  182. inner_eraseElements();
  183. var prom=checkIfPermanentRemoval(target);
  184. prom.then(function(confirm_val){ // then takes a function with 2 params, the arg of resolve func call and of reject.
  185. log("then in checkIfPermanentRemoval, permrm:",permrm,"confirm_val",confirm_val);
  186. var [permrm,item_sel]=confirm_val;
  187. halter.dialog=false;
  188. unhalt();
  189. if (permrm==Infinity) { // temp delete
  190. askedAlready=true;
  191. alert("You hit TEMP, item deleted. Ctrl-click from now until reload merely removes elements temporarily (esc to undo). ");
  192. last_one_deleted=$(item_sel);
  193. last_one_deleted.replaceWith("<placeholder delcnt="+(++delcnt)+">");
  194. console.log("install escape catch");
  195. escapeCatch(function(){
  196. $("placeholder").replaceWith(last_one_deleted); },"perm");
  197. }
  198. log("checkIfPermanentRemoval, permrm",permrm);
  199. // if (permrm!=undefined) inner_eraseElements("click"); //undefined==>escape (cancel)
  200. if (permrm!=false) inner_eraseElements("click"); //undefined==>escape (cancel)
  201. });
  202. prom.catch(function (a){log("caught in checkIfPermanentRemoval",a); unhalt();halter.dialog=false;});
  203. } // endif !askedAlready
  204. else {
  205. last_one_deleted=$(target); //temp delete
  206. $("placeholder").remove();
  207. last_one_deleted.replaceWith("<placeholder delcnt="+(++delcnt)+">");
  208. console.log("set last_one_deleted:",last_one_deleted, "placeholder",$("<placeholder>"));
  209. }
  210. if(!halter.dialog) { unhalt();}
  211. return false;
  212.  
  213. function halter() { window.addEventListener("beforeunload", handlehalt);}
  214. function handlehalt(event) {
  215. event.returnValue="a message to stay";
  216. //log("event beforeunload: ",event.returnValue);
  217. return event.returnValue;
  218. };
  219. function unhalt(){ window.removeEventListener("beforeunload",handlehalt,false);}
  220. } catch(e) { console.log("Click handling error:"+(e.lineNumber),e,e.stack);unhalt(); }} //handleClick()
  221.  
  222. 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.
  223. var dialog, p=new Promise((resolve,reject)=>{
  224. dialog=sprompt_inner(tex,initv,resolve,reject,cancel_btn,ok_btn,extra_btn);
  225. });
  226. p.dialog=dialog;
  227. return p;
  228. }
  229. function sconfirm(msg,cancelbtnText,okbtnText,extrabtnText) { return sprompt(msg,undefined,cancelbtnText,okbtnText,extrabtnText); }
  230. function salert(msg) { return sprompt(msg,undefined,-1,"OK"); }
  231.  
  232. //Resolution of promise returned is cancel:false, OK: true, extrabtn: Infinity;
  233.  
  234. 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 ""
  235. var that=arguments.callee; if (that.last_dfunc) that.last_dfunc("destroy"); // Only one modal allowed.
  236. var input_tag, input_style="width:80%;font-size:small;";
  237. var confirm_prompt=initval===undefined;
  238. if (!confirm_prompt) input_tag=initval.length<40 ? "input" : (input_style="width:95%;height:100px;","textarea");
  239. var content=$("<div class=sfswe-content tabindex=2 style='outline:none;white-space:pre-wrap;background:#fff0f0;'>"
  240. +"<div>"+pretext+"</div>"
  241. +(initval!==undefined ? "<"+input_tag+" spellcheck='false' style='"+input_style+"' tabindex='1'></"+input_tag+">":"")+"</div>");
  242. content.find("input:not(:checkbox),textarea").val(initval);
  243. try{content.resizable();}catch(e) {log("spromtinner(), err",e.lineNumber,e);}
  244. var sp1=$(document).scrollTop();
  245. var dfunc=content.dialog.bind(content);
  246. var dialog=content.dialog({
  247. 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
  248. close: function(e) { dialog.off("keydown"); $(document).scrollTop(sp1); if (e.key=="Escape") reject("Escape");}
  249. }).parent();
  250. var buttons={
  251. [cancelbtnText]: function(e) { if (confirm_prompt) resolve(false); else resolve([false, $(this).find("input,textarea").val()]); dfunc("close"); return false;},
  252. [okbtnText]: function(e) { if (confirm_prompt) resolve(true); else resolve([true,$(this).find("input,textarea").val() || ""]); dfunc("close"); return false;}
  253. };
  254. if(extrabtnText) buttons[extrabtnText]=function(e) { if (confirm_prompt) resolve(Infinity); else resolve([Infinity,$(this).find("input,textarea").val() || ""]); dfunc("close"); return false;};
  255. content.dialog("option","buttons",buttons);
  256. if (cancelbtnText==-1) { dialog.find("button").each(function(){ if (this.textContent=="-1") $(this).remove(); }); }
  257. dialog.wrap("<div class=sfswe-sprompt></div>"); // allows css rules to exclude other jqueryUi css on webpage from own settings, a
  258. dialog.keydown(function(e){ if (e.key == "Enter" && !/textarea/i.test(e.target.tagName)) $("button:contains("+okbtnText+")",this).click(); });
  259. dialog.css({"z-index":2147483647, width:550, position:"fixed", left:200, top: 50, background: "whitesmoke"}); //"#fff0e0"
  260. 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.
  261. dialog.draggable("option","handle", ".ui-dialog-buttonpane"); //
  262. dialog.resizable();
  263. // var maxH=innerHeight - (content.offset().top-$(window).scrollTop()) - 100;
  264. // content.css({"overflow-x":"hidden","max-height":maxH}); //innerHeight-dialog.position().top-$(".ui-dialog-buttonpane").height()}).scrollTop(0);
  265. setTimeout(function(){var ips=dialog.find("input,textarea");if (ips.length) ips.focus(); else content.focus();},100);
  266. that.last_dfunc=dfunc;
  267. return dialog; //.ui-dialog
  268. }catch(e) {log("hlightAndsetsel(), err",e.lineNumber,e);}}
  269.  
  270. function checkIfPermanentRemoval(target) { // called from click handler.
  271. var sconfirm_promise, checkif_resolve, checkif_reject;
  272. checkif_promise=new Promise((resolve,reject)=>{
  273. checkif_resolve=resolve;checkif_reject=reject;
  274. var parent=target.parentNode, index=0;
  275. 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. "
  276. +"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.
  277. +"Hit Temp button below for ctrl-click to erase element(s) temporarily and inhibit this prompting until reload."
  278. +"\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>";
  279. $(document).keypress(keypressHandler);
  280. sconfirm_promise=sconfirm(msg,"Cancel","OK","Temp"); ////////////////
  281. var dialog=sconfirm_promise.dialog;
  282. var buttonpane=dialog.find(".ui-dialog-buttonpane");
  283. 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>");
  284. 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>");
  285. dialog.find("a").click(e=>{
  286. log("click on a in OK");
  287. dialog.trigger($.Event("keydown",{keyCode:27,key:"Escape"})); // close prompt.
  288. eraseElementsCmd();});
  289. var input=$("#sfswe-seledip"), ip=input[0], div_surround=input.next();
  290. div_surround.click(e=>{ // a click on input & surround enables it.
  291. ip.disabled=false; ip.setSelectionRange(999,999);
  292. div_surround.css("display","none");
  293. input.focus();
  294. input.blur(e=>{ip.disabled=true; div_surround.css("display",""); });
  295. }); //
  296. hlightAndsetsel(target); // Also sets input value to selector!
  297. setTimeout(function(){dialog[0].scrollIntoView();},100);
  298. });//new Promise()
  299. close_of_prompt(sconfirm_promise, checkif_resolve, checkif_reject);
  300. log("Got back from close_of_prompt, close_of_prompt:",sconfirm_promise);
  301. return checkif_promise;
  302. }//end checkIfPermanentRemoval()
  303.  
  304. function close_of_prompt(sconfirm_promise, checkif_resolve, checkif_reject) {
  305. var nested_confirm, first_reply, complete_rm, reply_sel;
  306. sconfirm_promise.catch(function(reply){
  307. hlightAndsetsel(0,"off","restore");
  308. log("caught confirm prom");
  309. checkif_reject("caught");
  310. });
  311. nested_confirm=sconfirm_promise.then(function(reply){ ///////////////////////////////////////
  312. $(document).off('keypress');
  313. $(":data(pewiden-trace)").data("pewiden-trace",""); // remove trace
  314. var complete_rm=$("#sfswe-checkbox6:checked").length!=0;
  315. var webpage_only=$("#sfswe-checkbox7:checked").length!=0;
  316. log("complete_rm?",complete_rm, "reply:",reply);
  317. if (reply!=false) reply_sel=$("#sfswe-seledip").val().trim();
  318. if(reply!=true) {
  319. hlightAndsetsel(0,"off","restore");
  320. //if(reply==Infinity) checkif_resolve([reply,$(reply_sel).detach]); //$(reply_sel).hide(); // temp delete
  321. checkif_resolve([reply,reply_sel]);
  322. return;
  323. };
  324. sconfirm_promise.data=[reply_sel,complete_rm]; // use ES6 await?
  325. if (reply_sel) {
  326. let ancErased=$(reply_sel).closest(".Web-Eraser-ed"); //.closest, includes current.
  327. if (hidElementsListCmd("isthere?", reply_sel) || (ancErased.length && getHidElemsCmd("match el",ancErased))){
  328. alert("Already attempting erasure of the element specified or parent, if not being erased properly try "
  329. +"ticking the monitoring option or open 'Erase Web Elements' GM menu and hit its 'OK' button.\nInternal code:"
  330. +reply_sel+"\n\n Ancestor:"+nodeInfo(ancErased));
  331. console.info("Already erasing",ancErased,nodeInfo(ancErased),". Your selector",reply_sel);
  332. hlightAndsetsel(0,"off","restore");
  333. checkif_reject("Ancestor Already");
  334. return;
  335. }
  336. if (!webpage_only)
  337. hidElementsListCmd("add",reply_sel+" site");
  338. else
  339. hidElementsListCmd("add",reply_sel); // btn1 -> null, btn2 -> "<string>" null==undefined
  340. if (hidElementsListCmd("rm", $(reply_sel).find(".Web-Eraser-ed"))) console.info("Removed child selectors of",reply_sel);
  341. hlightAndsetsel(0,"off","restore");
  342. log("reply true, complete_rm@",complete_rm,"add sel:",reply_sel);
  343. if (complete_rm) zaplists.add(reply_sel);
  344. checkif_resolve([true,reply_sel]);
  345. } else { // empty reply.
  346. hlightAndsetsel(0,"off","restore");
  347. checkif_reject("empty");
  348. }
  349. });// sconfirm_promise.then /////////////////////////////////////////////
  350. }
  351.  
  352. function eraseElementsCmd() {
  353. // Called from GM script command menu and from clickable within ctrl-click prompt.
  354. //
  355. var erasedElems, no_sels;
  356. erasedElems=getHidElemsCmd("with site");
  357. no_sels = !erasedElems ? 0 : erasedElems.split(/,/).length;
  358. var prompt_promise=sprompt(
  359. "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 "
  360. +"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). "
  361. +"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."
  362. , erasedElems.replace(/,/g,", \n"));
  363. prompt_promise.then(function([btn,reply]){
  364. if (!btn) return; //cancel ==> null, undefined==> escape. (null is == to undefined!)
  365. config={monitor:config.monitor}; delete config.monitor[website];
  366. if ($("#sfswe-checkbox:checked").length) config.noAnimation="checked";
  367. if ($("#sfswe-checkbox2:checked").length) config.keepLayout="checked";
  368. if ($("#sfswe-checkbox3:checked").length) config.hideCurtains="checked";
  369. if ($("#sfswe-checkbox5:checked").length) config.monitor[website]="checked";
  370. if ($("#sfswe-checkbox4:checked").length) {
  371. toggleCurtains();
  372. let subpromt=sprompt("Please enter http address of curtain image to be used. If giving left and right images separate with a space. "
  373. +"Leave empty to reset. Accepts base64 image strings.","");
  374. 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");
  375. subpromt.then(function([btn2,reply2]){
  376. if (btn2) { setValue("ownImageAddr",reply2);
  377. curtain_icon=reply2||whitecurtains;
  378. curtain_slim_icon=reply2||whitecurtainsoriginal;
  379. curtain_wide_icon=reply2||whitecurtainstriple;
  380. $(".WebEraserCurtain").attr("src",curtain_icon); }
  381. toggleCurtains(); });
  382. } else {
  383. let duplicates={};
  384. reply=reply.replace(/\s*,\s*/g,",").replace(/(?=[^,])\n(?=[^,])/g,",").split(/,/); // , newline->comma if none; if no comma all is put in [0]
  385. log("Got reply array:",reply);
  386. if(reply.length) {
  387. page_erasedElems=[]; site_erasedElems=[];
  388. $(reply).each((i,str)=>{ //
  389. if (str=="") return;
  390. if (duplicates[str]) return;
  391. duplicates[str]=true;
  392. str=str.trim();
  393. if (/\ssite$/.test(str)) site_erasedElems.push(str.replace(/\ssite$/,""));
  394. else page_erasedElems.push(str);
  395. });
  396. site_erasedElems=site_erasedElems.toString();
  397. page_erasedElems=page_erasedElems.toString();
  398. }
  399. try{$(reply);} catch(e){alert("Bad selector given."); throw(e);}
  400. setValue("config",config);
  401. setValue(website+":erasedElems",site_erasedElems);
  402. setValue(webpage+":erasedElems",page_erasedElems);
  403. zaplists.update();
  404. //log("end awaaits of setvalue");
  405. openCurtains();
  406. $(".Web-Eraser-ed").each(function(){
  407. var self=$(this);
  408. self.css({display: self.data("sfswe-display"), visibility: self.data("sfswe-visibility")});
  409. self.removeClass("Web-Eraser-ed");
  410. });
  411. $(".CurtainRod").remove();
  412. //log("set tout")
  413. setTimeout(inner_eraseElements,1000,"prompt"); //'cos openCurtains takes time
  414. //inner_eraseElements("fromPrompt");
  415. }
  416. });//prompt_promise.then()
  417. var keep_layout=config.keepLayout;
  418. var dialog=prompt_promise.dialog;
  419. dialog.find(".ui-dialog-buttonpane").prepend(
  420. "<div class=sfswe-ticks style='float:left;font-size:10px;'>" //width:78%;
  421. +"<input id=sfswe-checkbox2 type=checkbox style='float:left;"+(!keep_layout?" margin:0 3px;":"")+"' "+keep_layout+"><label>Preserve layout (in general).</label>"
  422. +(keep_layout ? "<input id=sfswe-checkbox3 type=checkbox style='margin:0 3px 0 10px;height:12px;'"+(config.hideCurtains||"")+"><label>Also hide curtains.</label>" : "")
  423. +"<br><input id=sfswe-checkbox type=checkbox style='margin-left:3px;'"+(config.noAnimation||"")+"><label>Disable animation (in general).</label>"
  424. +"<br><input id=sfswe-checkbox4 type=checkbox style='margin-left:3px;'><label>Set your own curtains' image.</label>"
  425. +"<input id=sfswe-checkbox5 type=checkbox style='margin-left:15px;'"+(config.monitor[website]||"")+"><label>Monitor for new elements on this website.</label>"
  426. +"</div>"
  427. );
  428. dialog.find("input:checkbox").css({
  429. //cmtd out 8/17 "-moz-appearance":"none",
  430. height:12});
  431. dialog.find(".ui-dialog-content").attr("title","WebEraser userscript.\n"+webpage+"\n\nCurrent matches at this webpage:\n"+bodymsg());
  432. } //eraseElementsCmd()
  433.  
  434. function inner_eraseElements(from) {
  435. //
  436. // Called at page load and when user sets selector(s) for erasure.
  437. // 1. Go through uncurtained elements for erasure and do curtainClose (or css display to none) on each.
  438. // 2. Class each as "Web-Eraser-ed" and backup css values that might get changed.
  439. // 3. If changes were made log details to console and to logging div.
  440. //
  441. var erasedElems=getHidElemsCmd(), len=erasedElems.length, erasedElems_ar=erasedElems.split(/,/), count=0, nomatch=[];
  442. if (erasedElems_ar[0]=="") erasedElems_ar.shift(); //fix split's creation of array length one for empty string.
  443. var theErased=$(".Web-Eraser-ed"); theErased.removeClass("Web-Eraser-ed");
  444. log("inner_eraseElements, erasedElems_ar",erasedElems_ar, "from:",from);
  445. erasedElems_ar.forEach(function(sel,i){
  446. erasedElems=$(sel); //Array.from(document.querySelectorAll(sel)); //$(sel), jQ cannot find duplicate ids.
  447. erasedElems.each(function() {
  448. log("inner_eraseElements Found for sel",sel," el: ",this);
  449. var eld=this,el=$(eld); // 40msecs per 'each' loop.
  450. if(/delay|focus/.test(from)) { // skip curtains already closed.
  451. var crod=jQuery.data(eld,"rod-el"); //el.prev()[0];
  452. if (crod && /^sfswediv/i.test(crod[0].tagName)) {el.addClass("Web-Eraser-ed");return;}
  453. }
  454. markForTheCurtains(el,eld,sel);
  455. var no_anima=config.noAnimation, keep_layout=config.keepLayout;
  456. if (no_anima && !keep_layout) eld.style.setProperty("display","none","important");
  457. else if (el.css("display")!="none") closeCurtains(el, no_anima, measureForCurtains);
  458. count++;
  459. }); //erasedElems.each()
  460. if (erasedElems.length==0 && sel) nomatch.push(sel);
  461. }); //forEach()
  462. if (iframe || count==0) return;
  463. theErased=$(".Web-Eraser-ed");
  464. observeThings();
  465.  
  466. if (len==0) observeThings("off");
  467. if (theErased.length==0) return; ////////////////////
  468. if (nomatch.length) {
  469. console.info("WebEraser message: no match for the following selectors at",webpage+":");
  470. nomatch.forEach(nom=>console.info("\t",nom));
  471. }
  472. var ieemsg="Userscript WebEraser has selectors to hide. "+count+(count==1 ? " element that was":" elements that were")+" present on page at site: "+website
  473. +".\nSee GM menu command Erase Web Elements to check and edit selector list. "
  474. +(config.keepLayout ? "" : "Keep layout is not ticked.")
  475. +(config.noAnimation ? "Animation is off." : "")
  476. +(config.hideCurtains ? "Hide curtains is ticked." : "");
  477. theErased.each(function(i){
  478. var that=$(this);
  479. var sel=that.attr("selmatch-sfswe");
  480. var onzaplist=zaplists.which(sel); // 10 msecs to here from prev in closeCurtains() above.
  481. var rod=jQuery.data(this,"rod-el");
  482. var is_an_overlay=rod && rod.hasClass("sfswe-overlay"); //that.prev().hasClass("sfswe-overlay");
  483. ieemsg+="\n"+(i+1)+":"+sel;
  484. ieemsg+=".\t\t"
  485. +(is_an_overlay ? "=> Considered as an Overlay,takes up > 2/3 of window, deleted."
  486. : onzaplist.zap ? " => complete erasure."
  487. : onzaplist.keep_layout ? " => erase but keep layout."
  488. : "" );
  489. });
  490. ieemsg+="(phase:"+from+")";
  491. count=0;
  492. console.info(ieemsg);
  493. bodymsg(ieemsg.replace(/(.*\n){2}/,"")+" Whence: "+from+".","init");
  494. }
  495.  
  496. function closeCurtains(el, noAnimKeepLayout, finishedCB=x=>x) { //called from inner_eraseElements()
  497. //log("closeCurtains1, el:",el,noAnimKeepLayout,finishedCB,"sel:",el.attr("selmatch-sfswe")); //,"\n\nLog of Stack",logStack());
  498. var that=closeCurtains; if (!that.final_curtain) that.final_curtain=0;
  499. var hide_curtains=config.hideCurtains, keep_layout=config.keepLayout, wediv=el.children("sfswediv"), curtainRod, lrcurtains;
  500. var old_curtained=wediv.length ? jQuery.data(wediv[0],"covered-el") : null;
  501. if ( ! old_curtained || ! old_curtained.is(el))
  502. [curtainRod,lrcurtains]=createCurtains(el,noAnimKeepLayout);
  503. else { curtainRod=el.children("sfswediv"), lrcurtains=curtainRod.children();}
  504. curtainRod.css("display","");
  505. var onzaplist=zaplists.which(el); // 20 msecs from prev
  506. if (noAnimKeepLayout) {
  507. lrcurtains.css({width:"51%"});
  508. if (onzaplist.zap) { curtainRod.css({display:"none"}); el[0].style.setProperty("display","none","important");} // "none" triggers monitor if on.
  509. else if (onzaplist.keep_layout||hide_curtains||curtainRod.hasClass("sfswe-overlay")){
  510. curtainRod.css({visibility:"hidden",display:""});
  511. el[0].style.setProperty("visibility","hidden","important");
  512. }
  513. measureForCurtains();
  514. }
  515. else { // Do animated curtain closing, then, perhaps, fade out.
  516. that.final_curtain++;
  517. manimate(lrcurtains,["width",15,"%"],1000,2);
  518. manimate(lrcurtains,["width",51,"%",1000],1000,5,function(){ ///////////////////////Animation
  519. log("Anim end",lrcurtains,"Width of left curtain:",lrcurtains.css("width"));
  520. lrcurtains.css("width","51%");
  521. curtainRod.css("visibility","visible");
  522. el=jQuery.data(this.parentNode, "covered-el")||$();
  523. if (!keep_layout || curtainRod.hasClass("sfswe-overlay")||onzaplist.zap) {
  524. //console.log("Anim end, fade curtains");
  525. el.add(curtainRod).delay(200).fadeOut( // $.add here prepends.
  526. 500, function(){
  527. this.style.setProperty("display","none","important"); // triggers monitor if on.
  528. if (el[0]==this && --that.final_curtain==0) finishedCB();
  529. });
  530. }
  531. else if (hide_curtains||onzaplist.keep_layout) {
  532. //console.log("Anim end, fade out 2");
  533. el.add(curtainRod).delay(200).fadeOut(
  534. 1000, function(){
  535. this.style.setProperty("visibility","hidden","important");
  536. this.style.setProperty("display",$(this).data("sfswe-display"),"important"); //triggers monitor.
  537. curtainRod.css({visibility:"hidden",display:""});
  538. curtainRod.remove();
  539. if (el[0]==this && --that.final_curtain==0) finishedCB();
  540. });
  541. } else if (--that.final_curtain==0) {
  542. //console.log("Anim end, call CB");
  543. finishedCB();
  544. }
  545. }); //animate()
  546. }
  547. return false;
  548. } //closeCurtains()
  549.  
  550. function keypressHandler(event) { try{ //while prompt is open.
  551. var ip=$("#sfswe-seledip:enabled");
  552. if (ip.length) { //live typing of selector.
  553. setTimeout(ip=>{
  554. var cval=ip.val(), matched_els=[];
  555. try{matched_els=$(cval);} catch(e) {};// bad selector, transient
  556. if (matched_els.length) { // may unwind.
  557. hlightAndsetsel(0,"off",null,"mere_highlight");
  558. hlightAndsetsel($(cval),null,null,"mere_highlight"); }
  559. else hlightAndsetsel(0,"off","restore");
  560. },500,ip);
  561. } else { // widen/narrow
  562. switch(event.key) {
  563. case "w": widen(); break;
  564. case "n": narrow(); break;
  565. default: return; }
  566. return false;
  567. }
  568. } catch(e) {console.error("An key handler error:"+e+" "+e.lineNumber);} };
  569.  
  570. // GM_registerMenuCommand("Temporary web deleter, ctrl-click",function(){
  571. // window.addEventListener("mousedown",function(e){
  572. // if(e.ctrlKey) {
  573. // if (e.preventDefault) { e.preventDefault(); e.stopPropagation(); }
  574. // e.target.style.setProperty("display","none","important");
  575. // }
  576. // },true);
  577. // });
  578. //thousand's comma, call Number.toLocaleString()
  579. //if (iframe) console.log=x=>null; //logger(); // Logs from doc start.
  580.  
  581. async function init_globs() { // all globs asynchronously set.
  582. log("init_globs");
  583. page_erasedElems=(await getValue(webpage+":erasedElems","")).trim();
  584. //loader Failed here.
  585. site_erasedElems=(await getValue(website+":erasedElems","")).trim();
  586. elems_to_be_hid=getHidElemsCmd();
  587. zaplists=new zaplist_composite();
  588. await zaplists.update(); //depends on site/page_erasedElems being read first.
  589.  
  590. ownImageAddr=await getValue("ownImageAddr","");
  591. whitecurtains=await GM.getResourceUrl("whiteCurtains");
  592. // Ensure visit to https matches getResourceUrl use of https or address as given in header w/wo ssl!
  593. whitecurtainsoriginal=await GM.getResourceUrl("whiteCurtainsOrig");
  594. whitecurtainstriple=await GM.getResourceUrl("whiteCurtainsTrpl");
  595. curtain_icon=ownImageAddr|| whitecurtains;
  596. curtain_slim_icon=await getValue("ownImageAddr","")||whitecurtainsoriginal;
  597. curtain_xslim_icon=await getValue("ownImageAddr","")||await GM.getResourceUrl("whiteCurtainsXsm");
  598. curtain_wide_icon=await getValue("ownImageAddr","")||whitecurtainstriple;
  599. config=await getValue("config",{keepLayout:"checked",monitor:{}});
  600. if (!config.monitor) config.monitor={};
  601. if(!jQuery.ui) { // GM4 loader problem. Another userscript is loaded that has jq but no jQuery-UI will clobber $.ui here.
  602. $=jQuery=jq_saved; // saved from when prior to async branch.
  603. console.log("GM4 FAILure, jq.ui clobbered, patching up.",jQuery.ui);
  604. }
  605. }
  606.  
  607. function installEventHandlers(phase2) {
  608. if(!phase2) {
  609. document.addEventListener("scroll", function(e){ if (!overlay) return; e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();},true);
  610. //window.addEventListener("click",handleClick,true);
  611. window.addEventListener("mousedown",handleClick,true);
  612. window.addEventListener("message", postMessageHandler,false);
  613. if(iframe) window.installedEHs=true;
  614. //console.log("installed handlers, window.mousedown, at:",location.href,". In iframe?",iframe);
  615. }
  616. else {
  617. $("iframe").each(function(){
  618. var fwin=this.contentWindow; try{ //perhaps permission error due to iframe origin.
  619. if (!fwin.installedEHs) {
  620. fwin.addEventListener.call(fwin,"mousedown",handleClick,true);
  621. fwin.addEventListener.call(fwin,"message", postMessageHandler2,false);
  622. //despite use of call(), event is still triggered in this context not in iframe's hence use of frameElement.
  623. }} catch(e){};
  624. });
  625. }
  626. }
  627.  
  628. function prevDef(e) { if (e.preventDefault) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); }
  629. } //else console.log("No preventDefault for event",e); }
  630.  
  631. function postMessageHandler(e){ //reads postMessage().
  632. if ( ! e.data.type || e.data.type!="sfswe-iframe-click") return;
  633. //log("Handle a PostMessage, iframe:",iframe, "code:",e.data.code,"data",e.data,[e]);
  634. if (iframe) {
  635. window.parent.postMessage({type:"sfswe-iframe-click",code:++e.data.code},"*");
  636. return;
  637. }
  638. var iframeEl;
  639. $("iframe, embed").each((i,el)=>{
  640. // log("cmp, el.cont",el.contentWindow," e.source",e.source," e.origin",e.origin,typeof e.origin);
  641. // log("getsvg",el.getSVGDocument,el);
  642. // log("src",e.data.src,"==",el.src); //xss perm denied, e.source.toString(),
  643. if (el.contentWindow==e.source ||e.data.src==el.src) { iframeEl=$(el); return false; }
  644. });
  645.  
  646. // var iframeEl=$("iframe, embed").filter(function(){
  647. // log("cmp, this.cont",this.contentWindow," e.src",e.source,"res:",this.contentWindow==e.source);
  648. // return this.contentWindow==e.source;
  649. // });
  650. handleClick({target:iframeEl[0],ctrlKey:true},"iframe_click");
  651. }
  652.  
  653. function getSelectorWithNearestId(target,exclude_classes) { // need extended jquery for :regexp
  654. ensure_jquery_extended();
  655. var sel, nearestNonNumericId=target.closest(":regexp(id,^\\D+$)").attr("id"), nnmi=nearestNonNumericId; //closest also checks target
  656. //console.log("nnmi",nnmi, "matches #els:",$("[id="+nnmi+"]").length);
  657. if (nnmi && $("[id="+nnmi+"]").length>1) { nnmi="";ignoreIdsDupped=true;} // Page error duplicate ids, ignore id.
  658. if (nnmi) nnmi=$("#"+nnmi).prop("tagName")+"#"+nnmi; //cos of jQ & multiple ids.
  659. if ($(nnmi).is(target)) sel=nnmi;
  660. else {
  661. sel=selector(target,$(nnmi),true,0,exclude_classes); //ok if nnmi is undefined id.
  662. if (!sel) sel=nnmi; //both target and $(nnmi) are same element.
  663. else if(nnmi) sel=nnmi+sel;
  664. }
  665. return sel;
  666. }
  667.  
  668. function getHidElemsCmd(cmd,el){
  669. var els, pels=page_erasedElems, sels=site_erasedElems;
  670. //console.log("Got sels as:",sels);
  671. switch(cmd) {
  672. case "match el": return el.is($(getHidElemsCmd()));
  673. case "count": return getHidElemsCmd().split(/,/).reduce(function(prev_res,sel){return prev_res+$(sel).length;},0);
  674. case "with site": sels=sels.replace(/,/g," site,")+(sels ? " site" : ""); // see reverse of this in hidElementsListCmd() and eraseElementsCmd().
  675. default: return pels + (sels && pels ? "," : "") + sels; // if (justpels_ar) return pels.split(","); //webpage elements.
  676. }
  677. }
  678.  
  679. function hidElementsListCmd(cmd,str,str2) {
  680. //console.log("hidElementsListCmd, cmd:",cmd, "str:",str,"str2:",str2, "HidElems:",getHidElemsCmd());
  681. var sitewide;
  682. switch(cmd) {
  683. case "add":
  684. if (hidElementsListCmd("isthere?",str)) return;
  685. if (/\ssite$/.test(str)) { sitewide=true; str=str.replace(/\s+site$/,""); }
  686. if (sitewide) site_erasedElems += site_erasedElems ? ","+str : str;
  687. else page_erasedElems += page_erasedElems ? ","+str : str;
  688. $(str).each(function() { $(this).data("sfswe-oldval", $(this).css(["display","visibility","height","width"])); });
  689. break;
  690. case "mv":
  691. if (hidElementsListCmd("rm",str)) str2+=" site";
  692. hidElementsListCmd("add",str2);
  693. return; //return needed to prevent saving of old values.
  694. case "rm":
  695. if (str instanceof $) { str.each(function(){ hidElementsListCmd("rm", $(this).attr("#selmatch-sfswe")); }); return str.length; }
  696. page_erasedElems=$.map(page_erasedElems.split(/,/),el=>el==str ? null : el.trim()).join(",");
  697. site_erasedElems=$.map(site_erasedElems.split(/,/),el=>el==str ? null : el.trim()).join(",");
  698. break;
  699. case "isthere?": //check if str is amongst hidden elements list.
  700. return getHidElemsCmd().split(/,/).includes(str);
  701. //return getHidElemsCmd().split(/,/).reduce((prev_res,next)=>prev_res||next==str,false);
  702. }
  703. log("hidElementsListCmd",cmd,str,str2,"SetValues: ",site_erasedElems,page_erasedElems);
  704. setValue(website+":erasedElems",site_erasedElems);
  705. setValue(webpage+":erasedElems",page_erasedElems);
  706. zaplists.update();
  707. return sitewide;
  708. }
  709.  
  710. //Blinks are double, one for selected elements, other is only when at top/bottom of narrow/widen chosen.
  711. function hlightAndsetsel(elem, off, restore, mere_highlight) {try{ //also updates prompt with elem's selector.
  712. if (!off) { // on
  713. elem=$(elem);
  714. if (elem.length==0) return;
  715. gpre_elem=gelem;
  716. gelem=$(elem);
  717. var newsel,fullsel,h=gelem.height(),w=gelem.width();
  718. console.info("W/e widen/narrow, element to highlight is",gelem, mere_highlight);
  719. if (!mere_highlight) { // not typed in but from widen/narrow etc.
  720. var selinput=$("#sfswe-seledip"), //sfs_pesel");
  721. elhtml=gelem[0].outerHTML.replace(gelem[0].innerHTML,"");
  722. newsel=getSelectorWithNearestId(gelem,tbcl+" "+rbcl+" Web-Eraser-ed");
  723. fullsel=selector(gelem,0,false,0,tbcl+" "+rbcl+" Web-Eraser-ed");
  724. gelems=$(newsel).not(gelem);
  725. selinput.val(newsel); //+"<pre style='font-size:14.4px;'>\n\tHTML in pre</pre>");
  726. selinput.prop("title", (newsel!=fullsel ? "Full selector:\n\n\t"+fullsel+"\n\n" : "")
  727. //+gelem[0].outerHTML.replace(/>.*/g,">").replace(/\s*</g,"<")
  728. +"Element html:\n"+elhtml
  729. +"\n\nElement style:\n"+myGetComputedStyle(gelem[0]));
  730. } //endif !mere_highlight
  731. updatePromptText(newsel,fullsel);
  732. gelem.data("pewiden-trace","true"); // if (!gelem.hasClass("pewiden-trace"))
  733. //!! gelem.parents().addBack().addClass(tbcl);
  734. // gelem.find(">:only-child").addClass(tbcl);
  735. gelem.add(gelems).toggleClass(rbcl);
  736. gelem.elh=gelem[0].style.height; gelem.elw=gelem[0].style.width;
  737. gelem.height(h- 2*border_width);gelem.width(w- 2*border_width);
  738. bblinker=setInterval(function(){ // normal "selected" blink.
  739. if (gelems.length) gelems.toggleClass(rbcl);
  740. else gelem.toggleClass(rbcl); //.css({borderColor:"red",borderWidth:"9px",borderStyle:"double"});
  741. if (plat_msedge) // catch of escape not working on msedge.
  742. if ($(".ui-dialog").css("display")=="none") hlightAndsetsel(0,"off","restore");
  743. },1200);
  744. }
  745. else { //off
  746. clearInterval(bblinker);
  747. gelem.removeClass(rbcl);
  748. gelem[0].style.height=gelem.elh; gelem[0].style.width=gelem.elw;
  749. //gelem.height(h+ 2*border_width);gelem.width(w+ 2*border_width);
  750. if (restore) $("."+tbcl).removeClass(tbcl);
  751. }
  752. }catch(e) {console.error("hlightAndsetsel(), err",e.lineNumber,e);}}
  753.  
  754.  
  755. function widen() { // .html() return &gt; encodings, .text() does not. tab as @emsp must be set with html() not text()
  756. var selinput=$("#sfswe-seledip");
  757. if (/[:.][^>]+$/.test(selinput.val())) {
  758. var newsel=selinput.val().trim().replace(/[:.][^:.]+$/,"");
  759. selinput.val(newsel);
  760. gelems=$(newsel);
  761. gelems.addClass(rbcl);
  762. updatePromptText();
  763. return;
  764. }
  765. if (gelems.length) { gelems.removeClass(rbcl);gelems=$();}
  766. var p=gelem.parent();
  767. if (p.is("body")) {
  768. blinkBorders(gelem); //blink double indicates top of hierarchy.
  769. return;
  770. }
  771. hlightAndsetsel(0,"off");
  772. hlightAndsetsel(p);
  773. }
  774.  
  775. function narrow() {
  776. if (gelems.length) {
  777. widen(); // nulls gelems.
  778. narrow(); // Follow gelem trace back to el.
  779. //narrow();
  780. return;
  781. }
  782. var trace=gelem.find(":data(pewiden-trace):first"); // trace left by hlightAndsetsel()
  783. if(trace.length==0) trace=gelem.find(">:only-child");
  784. if (trace.length==0) {
  785. blinkBorders(gelem);
  786. return;
  787. }
  788. hlightAndsetsel(0,"off");
  789. hlightAndsetsel(trace);
  790. }
  791.  
  792. function updatePromptText(newsel,fullsel) { // set text size tagname etc.
  793. var updated_text="";
  794. if (gelems.length<=1)
  795. updated_text="selected ("+gelem.prop("tagName").toLowerCase()+") element ("+(gelem.height()|0)+"x"+(gelem.width()|0)+"pixels)";
  796. else
  797. updated_text="selected "+gelems.length+" "+gelem.prop("tagName").toLowerCase()+"s";
  798. updated_text+=":";
  799. $("#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 "
  800. +(newsel!=fullsel ? "full position in hierarchy," : "")
  801. +" html and style settings of the selected element. ");
  802. $("#fsfpe-tagel").text(updated_text);
  803. }
  804.  
  805. function myGetComputedStyle(el) {
  806. if (!document.defaultView.getDefaultComputedStyle) return ""; // has no getDefaultComputedStyle().
  807. var roll="",defaultStyle=document.defaultView.getDefaultComputedStyle(el);
  808. var y=document.defaultView.getComputedStyle(el), val, val2, i=1;
  809. for (let prop in y) {
  810. if (/^[a-z]/.test(prop) && ! /[A-Z]/.test(prop) && (val=y[prop])
  811. && val!=defaultStyle[prop]) {
  812. if (val.trim) //just a type check
  813. if (val.startsWith("rgb")) val="#"+val.replace(/[^\d,]/g,"").split(/,/).map(x=>Number(x).toString(16)).join("");
  814. 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)
  815. roll+= prop +": "+val+"; ";
  816. if (i++%3==0) roll+="\n";
  817. } //endif
  818. }
  819. return roll;
  820. }
  821.  
  822.  
  823. function blinkBorders(elem, interval=150, times=4) { // borders must already be set.
  824. times*=2;
  825. var cnt=0,i=setInterval(function(){
  826. cnt++;
  827. elem.toggleClass(rbcl);
  828. //!!
  829. // elem.toggleClass(tbcl);
  830. if (cnt==times) {clearInterval(i);elem.removeClass(rbcl);}// interference so rm class.
  831. },interval);
  832. }
  833.  
  834. function ensure_jquery_extended() {
  835. if ($.expr[":"].regexp) return; // already extended, not yet clobbered.
  836. $.fn.reverse = Array.prototype.reverse;
  837. $.fn.swap = function(to) {
  838. var a=this.eq(0), b=$(to).eq(0);
  839. var tmp = $('<span>').hide();
  840. a.before(tmp);
  841. b.before(a);
  842. tmp.replaceWith(b);
  843. return;
  844. };
  845. $.easing["stepper"] = function (x, t, b, c, maxt) { // eg, see, console.log($.easing) for other funcs.
  846. // var y=c*(t/=maxt)*t + b;
  847. // if (x<0.4) y=0.1;
  848. //console.log(x);
  849. //return y;
  850. return x;
  851. };
  852. $.extend($.expr[':'], { // Check it's there with $.expr[":"].regexp.toString()
  853. regexp: function(currentobj, i, params, d) { //filter type function.
  854. params=params[3].split(/,/); //eg, [ 'regexp', 'regexp', '', 'className,promo$' ]
  855. var attr=params[0], re=params[1]; //eg, className, promo$
  856. if (attr=="class") attr="className";
  857. var val=currentobj[attr]+""||"";
  858. if (attr=="className") return val.split(/\s/).some(function(cl){return cl.match(re);});
  859. else return val.match(re);
  860. }}); //$.extend() //usage eg: $(“div:regexp(className,promo$)”);
  861.  
  862. (function($){
  863. $.event.special.destroyed = {
  864. remove: function(o) {
  865. if (o.handler) {
  866. o.handler();
  867. }
  868. }
  869. }; })($); //Usage: $("#anid").bind('destroyed', function() {// do stuff}) // only for is jQ removed el.
  870. } //extend_jquery()
  871.  
  872. 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.
  873. anc=$(anc).eq(0); //apply only to first ancestor.
  874. if (anc.length==0) anc=$(document.body.parentNode); // !anc wouldnt work for a jq obj.
  875. desc=$(desc);
  876. if ( (desc.closest(anc).length==0 || desc.length!=1) && !recursed) {
  877. console.info("Too many elements or descendant may not related to ancestor:");
  878. console.info("Descendant is:"+selector(desc,0,0,true));
  879. console.info("Ancestor is:"+selector(anc,0,0,true)+".");
  880. return;
  881. }
  882. // Last element is highest in node tree for .parentsUntil();
  883. var sel=
  884. desc.add(desc.parentsUntil(anc)) // up to but not including.
  885. .reverse()
  886. .map(function() { // works from bottom up to ancestor, hence need for reverse().
  887. var t=$(this), tag=this.tagName.toLowerCase(), nth=t.prevAll(tag).length+1, id="", cl, nthcl;
  888. //id=this.id.replace(/^\s*\b\s*/,"#"); if (!ignoreIdsDupped) id="";
  889. cl=(t.attr("class")||"").trim(); // Don't use this.className (animated string issue)
  890. cl=cl.split(/\s+/).join(".").prefix(".");
  891. if (exclude_classes) cl=cl.replace(RegExp(".("+exclude_classes.replace(/ /g,"|")+")","g"),"");
  892. if (no_numerals && /\d/.test(id)) id="";
  893. if (no_numerals && /\d/.test(cl)) cl="";
  894. if ( (cl && t.siblings(tag+cl).length==0)
  895. || id
  896. || t.siblings(tag).length==0)
  897. nth=0;
  898. else if (cl && t.siblings(tag+cl).length!=0) {
  899. cl+=":eq("+t.prevAll(tag+cl).length+")"; //jQuery only has :eq()
  900. nth=0;
  901. }
  902. return tag+(nth?":nth-of-type("+nth+")":"")+id+cl; ////////////////////nth-of-type is One-indexed.
  903. }) //map()
  904. .get() //
  905. .reverse()
  906. .join(">");
  907. if (desc.is(anc.find(">"+sel))) {
  908. if (anc.is(document.body.parentNode)) return "html>" + sel;
  909. return ">"+sel;
  910. } else {
  911. console.info("Selector result:\n\t"+sel+" Not findable in ancestor, nor in body's parent.");
  912. if ($(sel).length) return sel; //Its the very top element, <HTML>.
  913. }} catch(e){logError("Can't get selector for "+desc,e); }} //fixBadCharsInClass(desc);}
  914.  
  915.  
  916. function fixBadCharsInClass(obj) { //official chars allowed in class, throw error in jquery selection.
  917. obj.parents().addBack().each(function(){ this.className=this.className.replace(/[^\s_a-zA-Z0-9-]/g,""); });
  918. }
  919.  
  920. function markForTheCurtains(el,eld,sel,unmark) {
  921. if (!unmark) {
  922. el.css({overflow:"hidden"}).addClass("Web-Eraser-ed").attr("selmatch-sfswe",sel) //hidden, so height not 0.
  923. .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.
  924. }
  925. else el.css({overflow:el.data("overflow")}).removeClass("Web-Eraser-ed").attr("selmatch-sfswe",""); //hidden, so height not 0.
  926. log("end markForTheCurtains, classes:",eld.className);
  927. }
  928.  
  929. function reattachTornCurtains(curtains=$(".CurtainRod")) {try{
  930. var torn=false;
  931. curtains.each(function(){
  932. var that=$(this), el=jQuery.data(this,"covered-el")||$();
  933. if (el.parent().length==0 || !el.hasClass("Web-Eraser-ed")) {
  934. torn=true;
  935. that.addClass("sfswe-delete","true");
  936. //that.remove();
  937. } });
  938. $(".sfswe-delete").remove();
  939. if (torn) inner_eraseElements();
  940. } catch(e){console.error("WebEraser reattachTornCurtains(), error@",e.lineNumber,e);}}
  941.  
  942. function measureForCurtains(curtains=$(".CurtainRod")) {
  943. curtains.each(function(){
  944. //console.log("measureForCurtains for el(data-covered) as:",jQuery.data(this,"covered-el"));
  945. var that=$(this), el=jQuery.data(this,"covered-el"); // $.data seems to lose its info when another userscript is also running, jQuery.data works.
  946. if(!el) {console.error("noel");el=$();}
  947.  
  948. 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.
  949. if (!el.hasClass("Web-Eraser-ed")) {
  950. el.addClass("Web-Eraser-ed");
  951. el.css({overflow:"hidden"});
  952. }
  953. //var offset=moffset(el);
  954. //that.css(offset);
  955. //that.css({height:h,width:w});
  956. //this.style.setProperty("width",w+"px","important");
  957.  
  958. if(!that.hasClass("outie")) {
  959. that.css({left:0,top:0});
  960. this.style.setProperty("width","100%","important");
  961. this.style.setProperty("height","100%","important");
  962. }else {
  963. var offset=moffset(el);
  964. that.css(offset).css({height:h,width:w});
  965. this.style.setProperty("width",w+"px","important");
  966. }
  967.  
  968. });};
  969.  
  970. function bodymsg(str,init) { // Append string to a <pre> that is initially appended to body.
  971. if ($("#sfswe-div-logger").length==0) $("body").prepend("<pre style='display:none;' id=sfswe-div-logger><pre class=init></pre></pre>");
  972. var sfsprelog=$("#sfswe-div-logger");
  973. var initpre=sfsprelog.find(".init");
  974. if (str) if (init) initpre.text(initpre.text()+"\n"+str+"\n"); //b.attr("sfswe-message",str);bodymsg.init=str;}
  975. else {
  976. if (str==bodymsg.str) sfsprelog.append(".");
  977. else {
  978. sfsprelog.append("\n"+str);
  979. console.info("WebEraser Monitor: "+str);
  980. bodymsg.str=str;
  981. }
  982. }
  983. return initpre.text();
  984. }
  985.  
  986. function observeThings(disable) { // call will start or if running reset monitoring, with param, it disables.
  987. var that=arguments.callee; that.off=[];
  988. if (that.obs1) { try { that.obs1.disconnect(); that.obs2.disconnect();} catch(e){
  989. console.log("Error during turn off of observations,",e); } }
  990. if (disable || ! config.monitor[website]) return;
  991.  
  992. var a,b,sels=getHidElemsCmd(),
  993. nomonitor=set=>{ if (set==1) { that.off.push(true); a=that.obs1.takeRecords(); b=that.obs2.takeRecords();
  994. //if(a.length ||b.length) console.log("TOOK records");
  995. } // jquery get causes set, hence inf.loop.
  996. if (set==0) { that.off.pop(); a=that.obs1.takeRecords(); b=that.obs2.takeRecords();
  997. //if(a.length ||b.length) console.log("0TOOK records");
  998. } return that.off.slice(-1)[0]; };
  999. var parseCssText=str=>JSON.parse("{" + (str||"").replace(/[\w-]+(?=:)/g,'"$&"').replace(/:\s*(.+?)(?=;)/g,':"$1"').replace(/;/g,",").slice(0,-1) + "}");
  1000. console.info("WebEraser message: Monitoring elements that match given selectors for creation and display and to be erased on sight.");
  1001. $(sels).each((i,el)=>$(el).data("sfswe-oldval", $(el).css(["display","visibility","height","width"])) ); //copy of style obj but dead (eg, cssText not updated).
  1002. obs1_connect(sels);
  1003. function obs1_connect(selectors) {
  1004. that.obs1=attrModifiedListener(document,selectors,["style","class","id"],function(mutrecs) {
  1005. if (nomonitor()) return;
  1006. nomonitor(1);
  1007. var rec=mutrecs[0], t=rec.target, target=$(t), attr=rec.attributeName;
  1008. var oldval=target.data("sfswe-oldval"), currval=target.css(["display","visibility","height","width"]);
  1009. //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(" ") );
  1010. //ldval=parseCssText(mutrecs[0].oldValue); //var moldval=parseCssText(rec.oldValue);
  1011. var objsel=target.attr("selmatch-sfswe");
  1012. if (!objsel) {
  1013. target.data("sfswe-oldval", target.css(["display","visibility","height","width"]));
  1014. markForTheCurtains(target,t,findMatchingSelector(target,selectors));
  1015. }
  1016. if (!oldval && /class|id/.test(attr)) { //&& target.prev("sfswediv")[0]) {
  1017. var newlen=that.obs1.add(target);
  1018. oldval={};
  1019. }
  1020. if (currval.display=="none" && oldval.display!="none") {
  1021. bodymsg("change-nodisplay:"+target.attr("selmatch-sfswe"));
  1022. target.children("sfswediv").css("display","none");
  1023. measureForCurtains();
  1024. } else if (currval.display!="none" && (oldval.display=="none" || oldval.display==undefined )) {
  1025. bodymsg("change-display:"+target.attr("selmatch-sfswe"));
  1026. target.children("sfswediv").css("display","");
  1027. closeCurtains(target); //,true); //no animation since asynch anime will trigger too many mutation records.
  1028. }
  1029. if ( parseInt(currval.height)|0 - parseInt(oldval.height)|0) {
  1030. bodymsg("change-height:"+nodeInfo(target)+" "+currval.height);
  1031. measureForCurtains();
  1032. } else if ( parseInt(currval.width)|0 - parseInt(oldval.width)|0) {
  1033. bodymsg("change-width:"+nodeInfo(target)+" "+currval.width);
  1034. measureForCurtains();
  1035. } else if (currval.visibility!=oldval.visibility)
  1036. bodymsg("change-visibility:"+nodeInfo(target));
  1037. // if (currval.visibility=="visible") {
  1038. // bodymsg("change-display:"+target.attr("selmatch-sfswe"));
  1039. // target.prev().css("display","");
  1040. // closeCurtains(target,true); //no animation since asynch anime will trigger too many mutation records.
  1041. // } else if (currval.visible!="visible") {
  1042. target.data("sfswe-oldval",currval);
  1043. // change-visibility?
  1044. //}); //forEach
  1045. nomonitor(0);
  1046. }); // attrModifiedListener(...
  1047. } // obs1_connect()
  1048. that.obs2=nodeMutationListener(document,sels, function(foundArrayOfNodes, parentOfMutation,removed) {
  1049. if (nomonitor()) return;
  1050. nomonitor(1);
  1051. foundArrayOfNodes.forEach(node=>{ // A flattened subtree, if node was again removed quickly it may have no parent.
  1052. var jQnode=$(node);
  1053. if (!removed) { // new node inserted.
  1054. jQnode.data("sfswe-oldval", jQnode.css(["display","visibility","height","width"]));
  1055. var foundsel=findMatchingSelector(jQnode,sels);
  1056. bodymsg("new-node:"+foundsel);
  1057. markForTheCurtains(jQnode,node,foundsel);
  1058. closeCurtains(jQnode,false,measureForCurtains); //nomonitor(0); },300);
  1059. } else { // node removed
  1060. //if(jQnode.attr("cc")) {
  1061. bodymsg("node-delete:"+jQnode.attr("selmatch-sfswe"));
  1062. $(".CurtainRod[cc='"+jQnode.attr("cc")+"']").remove(); //.filter(function(){return $(this).data()})
  1063. measureForCurtains();
  1064. //}
  1065. }
  1066. });//forEach
  1067. nomonitor(0);
  1068. },true); //nodeMutationListener()
  1069. }
  1070.  
  1071. function findMatchingSelector(obj,sels) {
  1072. return sels.split(/,/).find(sel=>obj.is(sel));
  1073. }
  1074.  
  1075. function openCurtains(zap_or_keep="",curtains=$(".WebEraserCurtain")) { // called from ctrl-click with curtains, eraseElementsCmd() w/o curtains, and lrcurtains.click sets "keep"
  1076. log("openCurtains",zap_or_keep,"curtains:",curtains);
  1077. setTimeout(function() {
  1078. curtains.each(function() { $(this).parent().css("visibility","hidden");});
  1079. manimate(curtains,["width",0,"%"],3500,8,function() {
  1080. var that=$(this), erased_el=jQuery.data(this.parentNode,"covered-el");
  1081. var sel=erased_el.attr("selmatch-sfswe");
  1082. bodymsg("opened curtains for sel:"+sel+", cc:"+erased_el.attr("cc"));
  1083. switch(zap_or_keep[0]) { // z: zap from layout, k: keep layout, t temporarily rm curtains, a: alt rm erasure
  1084. case "z": zaplists.add(sel);erased_el.css("display","none");measureForCurtains();console.info("Completely erased,",sel+".");break;
  1085. case "k": zaplists.add(sel,"keep");;erased_el[0].style.setProperty("visibility","hidden","important");console.info("Hidden for layout,",sel+".");break; //keep_layout
  1086. case "t": that.parent().css("display","none");break; //tzap
  1087. case "a": hidElementsListCmd("rm",sel); observeThings(); that.parent().remove();markForTheCurtains(erased_el,0,0,"unmark"); break; //azap
  1088. }
  1089. //erased_el.prev().css({display:"none"});
  1090. });
  1091. },1000);
  1092. return false;
  1093. }
  1094.  
  1095. // Outline overview of layout:
  1096. //
  1097. // <DIV id=xyz class=Web-Eraser-ed selmatch-sfswe="DIV#xyz"> // target el, for covering. el.children("sfswediv") has data covered-el to here.
  1098. // <sfswediv class=CurtainRod cc=n data.coveredEl=divtarget>
  1099. // <img class=webEraserCurtain sfswe-left>
  1100. // <img> class=webEraserCurtain sfswe-right>
  1101. // </sfswediv>
  1102. // </DIV>
  1103.  
  1104. //
  1105.  
  1106. function createCurtains(el, noAnimKeepLayout) {
  1107. var h=el.outerHeight()|0,w=el.outerWidth()|0, area=h*w, iw=w/2, //pos= moffset(el),
  1108. warea=window.innerHeight*window.innerWidth, csspos=el.css("position");
  1109. log("createCurtains ",noAnimKeepLayout,"h/w",h,w," el:",el);
  1110. // 9 msecs to here from function start.
  1111. var lsrc=curtain_icon.split(/\s+/)[0], rsrc=curtain_icon.split(/\s+/).slice(-1); //last string
  1112. //if (!getValue("ownImageAddr","")) switch(true) {
  1113. if(!ownImageAddr) switch(true) {
  1114. case w<250: lsrc=rsrc=curtain_xslim_icon;break;
  1115. case w<500: lsrc=rsrc=curtain_slim_icon;break;
  1116. case w>800: lsrc=rsrc=curtain_wide_icon;break; }
  1117. var lcurtain=$("<img class='WebEraserCurtain sfswe-left' style='left:0;position:absolute;height:100%;visibility:visible;'>");
  1118. lcurtain.attr("src",lsrc);
  1119. setTimeout(()=>{
  1120. if (lcurtain[0].complete||plat_chrome) return;
  1121. lcurtain[0].src="https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains.orig.jpg";
  1122. rcurtain[0].src="https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains.orig.jpg";
  1123. },500);
  1124. var rcurtain=$("<img class='WebEraserCurtain sfswe-right' style='right:0;position:absolute;height:100%;visibility:visible;' src="+rsrc+"></img>"),
  1125. 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.
  1126. lrcurtains=lcurtain.add(rcurtain), sel=el.attr("selmatch-sfswe");
  1127. //Absolute is relative to nearest non-statically positioned ancestor, this is returned from elem.offsetParent.
  1128. el.attr("cc",curtain_cnt);
  1129. curtainRod.append(lcurtain,rcurtain);
  1130. 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."
  1131. +"\n\nSelector is: "+sel+", webpage is:"+webpage+".";
  1132.  
  1133. lrcurtains.contextmenu(e=>(eraseElementsCmd(),false));
  1134. lrcurtains.click(function({ctrlKey:ctrl,shiftKey:shift,altKey:alt,target:target}) {
  1135. var that=$(this),lrcurtains=that.add(that.siblings());
  1136. log("lrcurtains.click alt",alt,"lrcurtains:",lrcurtains,"this:",this);
  1137. if (!(alt||shift)) return;
  1138. if (ctrl&&shift) alert("Curtained target is,"+target,"lrcurtains:",lrcurtains,"this",this);
  1139. else if (shift) openCurtains("keep_layout",lrcurtains);
  1140. else if (alt) openCurtains("azap",lrcurtains);
  1141. else if (ctrl&&alt) that.parent().focus();
  1142. return false;
  1143. });
  1144. lrcurtains.dblclick(e=>openCurtains("tzap",lrcurtains));
  1145. curtainRod.dblclick(e=>openCurtains("tzap",lrcurtains));
  1146. el.dblclick(e=>closeCurtains(el)); el.mousedown(e=>false); el.mouseup(e=>false); el.click(e=>false);
  1147. curtainRod.keypress(moveRod);
  1148. //curtainRod.css({height:h,width:w}).css(pos).data({coveredEl:el,selmatchSfswe:sel});
  1149. //curtainRod.css({height:h,width:w}); //.css(pos);
  1150.  
  1151. curtainRod[0].style.setProperty("float","none","important");
  1152. curtainRod[0].style.setProperty("width",w+"px","important");
  1153. jQuery.data(curtainRod[0],"coveredEl",el);
  1154. jQuery.data(el[0],"rodEl",curtainRod);
  1155. //log("data covered-el",jQuery.data(curtainRod[0],"covered-el"),"for rod",curtainRod[0]);
  1156. //log("\nAnd rod-el is ",jQuery.data(el[0],"rod-el"),"for el",el[0]);
  1157. lrcurtains.css({ width: (!noAnimKeepLayout ? 0 : "51%" )}); // Initial width of each curtain.
  1158. var portions=area/warea*100|0; //curtainRod.attr("init-calc",(calc|0)+" "+portions);
  1159. if (portions>=60) { //>75% of window is covered.
  1160. var visible_area;
  1161. with (Math) {visible_area=min(w,window.innerWidth)*min(h,window.innerHeight);}
  1162. if (visible_area>=warea*0.6) {
  1163. lcurtain.css({left:"10%"});
  1164. curtainRod.css({width:"80%",top:"10%"}).addClass("sfswe-overlay");
  1165. lrcurtains.css({height:h*0.8});
  1166. setTimeout(x=>$("html, body").css("overflow",(i,v) =>
  1167. v=="hidden" ? "auto": null).css("position",(i,v) => v=="fixed" ? "static": null),4000);
  1168. overlay=true;
  1169. //First event listener can stop prop to ones added later, ideally would be added at doc-start.
  1170. console.info("This element, chosen for erasure, is an Overlay (>2/3 covered, "+portions+"%, "+h+"x"+w+"): ", sel, el);}}
  1171. assertZ(el);
  1172. if(!el.is("iframe,img,canvas")) {
  1173. curtainRod.css({height:"100%",width:"100%",left:0,top:0});
  1174. el.prepend(curtainRod); ////////////////////
  1175. curtainRod.parent().css("position","relative"); // ensure also that left and top are 0.
  1176. }
  1177. else {
  1178. curtainRod.addClass("outie");
  1179. let pos=moffset(el);
  1180. log("OUTIE pos",pos,h,w, "$pos:",el.position());
  1181. curtainRod.css({height:h,width:w}).css(pos); // curtainRod.css({height:"100%",width:"100%",left:0,top:0});
  1182. el.wrap("<div class=sfswe-contner>");
  1183. el.before(curtainRod);
  1184. }
  1185. return [curtainRod,lrcurtains];
  1186. }
  1187.  
  1188. function moveRod(e) {
  1189. if (e.key=="w"||e.key=="n") {
  1190. let newel, rod=$(this), el=jQuery.data(this,"covered-el")||$(), p=el.parent(), newsel, oldsel=el.attr("selmatch-sfswe");
  1191. var trace=el.find(":data(pewiden-trace):first");
  1192. if(trace.length==0) trace=el.find(">:only-child");
  1193. if (e.key=="n") newel=trace; //narrow
  1194. else newel=p; // widen
  1195. if (newel.length==0 || newel.is("body")) { rod.focus();$("body").blur();rod.focus(); return false;}
  1196. newsel=selector(newel,0,false,0,"Web-Eraser-ed");
  1197. el.data("pewiden-trace","true");
  1198. hidElementsListCmd("mv", oldsel, newsel);
  1199. newel.before(rod);
  1200. jQuery.data(this,"covererEl",newel);
  1201. markForTheCurtains(el,null,null,"unmark");
  1202. markForTheCurtains(newel,newel[0],newsel);
  1203. rod[0].title=rod[0].title.replace(/\nSelector is:.*\./,"\nSelector is:"+newsel+".");
  1204. measureForCurtains();
  1205. rod.focus();
  1206. } else if (e.key=="l") { //lighten
  1207. var rod=$(this);
  1208. var op=rod.css("opacity");
  1209. rod.css("opacity",op*0.8);
  1210. setTimeout(x=>rod.css("opacity",rod.css("opacity")*1.25),10000);
  1211. } else if (e.key=="s") {
  1212. var sfsprelog=$("#sfswe-div-logger");
  1213. sfsprelog.css("display","");
  1214. sfsprelog[0].scrollIntoView();
  1215. }
  1216. return false;
  1217. }
  1218.  
  1219. function toggleCurtains() {
  1220. var that=arguments.callee;
  1221. $(".CurtainRod").each(function(){
  1222. if (!that.xor) {manimate($(".WebEraserCurtain",this),["width",51,"%"],2000,12);}
  1223. else manimate($(".WebEraserCurtain",this),["width", $(this).data("init-width"),"%"],4000,8);
  1224. });
  1225. }
  1226.  
  1227. function zaplist_composite() { // composite pattern. 4 objs. Those on zaplist are for complete erasure, but may keep layout.
  1228. if (iframe) return;
  1229. var zlists=[new zaplist(webpage),new zaplist(website),new zaplist(webpage,"kl"),new zaplist(website,"kl")];
  1230. this.add=function(sel,keep_layout){
  1231. zlists.forEach(function(el) { el.add(sel,keep_layout);}); };
  1232. this.contains=function(el){ // may be a dom/jq object or a string selector.
  1233. return zlists.some(function(list) { return list.contains(el);}); };
  1234. this.which=function(el) { // The 2 bits returned tell if & on which zaplist the elem is.
  1235. if (this.contains(el)) {
  1236. var has_keep_layout=zlists.map(v => v.contains(el)).includes("kl");
  1237. return {keep_layout:has_keep_layout,zap:!has_keep_layout};
  1238. }
  1239. return {keep_layout:false,zap:false};
  1240. };
  1241. this.update=function(sel){ zlists.forEach(function(el) { el.update();}); };
  1242. this.toString=()=>"[object zaplist_composite]";
  1243. }
  1244.  
  1245. function zaplist(key,keytype) {
  1246. var fullkey=key+":zaplist"+(keytype? ":"+keytype : "");
  1247. var savelist=function() { var p=setValue(fullkey,list);
  1248. if (!list.length && GM.deleteValue) p=GM.deleteValue(fullkey);
  1249. //log("saved zaplist fullkey",fullkey,", list:",list,"getval");
  1250. return p;
  1251. };
  1252. var readlist=function() { return getValue(fullkey,[]); };
  1253. var list; //console.log("zap inited:",key,keytype);
  1254. this.add=async function(str,kl) {
  1255. log("zaplist add, str:",str,"in kl:",kl,"this.fullkey:",fullkey);
  1256. if (!!kl != !!keytype) return;
  1257. list.push(str);
  1258. if((await getValue(key+":erasedElems","")).split(/,/).includes(str)) {
  1259. await savelist();
  1260. } else { log("pop goes the attempt");list.pop();}
  1261. };
  1262. this.contains=function(jqobjOrStr){
  1263. //log("zap check if list contains obj:",jqobjOrStr,"within its \nlist:",list,"FUll key",fullkey);
  1264. if (list.length==0) return;
  1265. if (jqobjOrStr.attr) jqobjOrStr=jqobjOrStr.attr("selmatch-sfswe");
  1266. if (list.indexOf(jqobjOrStr) != -1)
  1267. return keytype||"zap";
  1268. };
  1269. this.rm=function(str) {
  1270. var i=list.indexOf(str);
  1271. if (i!=-1) list.splice(i,1);
  1272. savelist();
  1273. };
  1274. this.update=async function() { // If sels removed from main list also remove from zaplists.
  1275. if(!list) list=await readlist();
  1276. //log("zaplist.update key:",fullkey,"read list:",list);
  1277. //if (list.length==0) return;
  1278. var strs_ar=getHidElemsCmd().split(/,/);
  1279. list=list.filter(function (lel) {
  1280. return strs_ar.includes(lel);
  1281. });
  1282. savelist();
  1283. };
  1284. this.toString=()=>"[object zaplist]";
  1285. }
  1286.  
  1287. function moffset(elem, eld=elem[0]) { try{
  1288. if ( elem.find("*").addBack().filter(function(){
  1289. return $(this).css("position").includes("fixed");
  1290. }).length ) // if (elem.css("position").includes("fixed"))
  1291. return Object.assign(elem.position(),{position:"fixed"});
  1292. var dominPar=elem.offsetParent()[0]; // gets closest element that is positioned (ie, non static);
  1293. return left_top(elem);
  1294. function left_top(elem) {
  1295. var {left,top}= elem.position(); // something sets & unset margintop or left during something here for some reason, margins and floating els may disaffect calc!
  1296. let margl=parseInt(elem.css('margin-left')), margt=parseInt(elem.css('margin-top'));
  1297. //let bordl=parseInt(elem.css('border-left-width')), bordt=parseInt(elem.css('border-top-width'));
  1298. var x = left + margl, y = top + margt;
  1299. do {
  1300. elem = elem.offsetParent();
  1301. if (elem.is(dominPar) || elem.is("html")) break;
  1302. let {left,top}=elem.position(); // something sets & unset margintop during something here for some reason, margins and floating els may disaffect calc!
  1303. x += left; y += top;
  1304. } while (true)
  1305. if (y) y--;
  1306. return { left: x, top: y };
  1307. }
  1308. } catch(e){ console.log("Error moffset",e); }}
  1309.  
  1310. function assertZ(el){
  1311. var dominPar=el.offsetParent();
  1312. // var tnames=["transform","-webkit-transform","-webkit-perspective"];
  1313. var tnames=["transform","perspective"]; // jquery adds vendor suffixes, eg -webkit-
  1314. el.parentsUntil(dominPar).addBack().each(function(){
  1315. var that=$(this), tforms=that.css(tnames),tf={};
  1316. //log("assertZ dominpar:",dominPar,"tforms:",tforms);
  1317. if(Object.values(tforms).some(x=>!/none/.test(x))) {
  1318. tnames.forEach(name=>tf[name]="none");
  1319. that.css(tf);that.addClass("assertedZ");
  1320. }
  1321. //log("assertz changed css",tforms,"\n",tforms,"now it is: ",that.css(tnames));
  1322. });
  1323. }
  1324.  
  1325. //
  1326. // MutationObserver functions. Eg, var obs=nodeInsertedListener(document,"#results", myCBfunc); function myCBfunc(foundArrayOfNodes, DOMparentOfMutation);
  1327. // Requires jQuery.
  1328. // See https://www.w3.org/TR/dom/#mutationrecord for details of the object sent to the callback for each change.
  1329. // Four functions available here:
  1330. // 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.
  1331. // Return false from callback to ditch out.
  1332.  
  1333. function nodeInsertedListener(target, selector, callback, include_subnodes) {
  1334. return nodeMutation(target,selector,callback,1, include_subnodes);
  1335. }
  1336. function nodeRemovedListener(target, selector, callback, include_subnodes) {
  1337. return nodeMutation(target,selector,callback,2, include_subnodes);
  1338. }
  1339. function nodeMutationListener(target, selector, callback, include_subnodes) { //inserted or removed, callback's 3rd parameter is true if nodes were removed.
  1340. return nodeMutation(target,selector,callback,3, include_subnodes);
  1341. }
  1342. function attrModifiedListener(target, selectors, attr, callback) { //attr is array or is not set. Callback always has same target in each mutrec.
  1343. var attr_obs=new MutationObserver(attrObserver), jQcollection=$(selectors);
  1344. var config={ subtree:true, attributes:true, attributeOldValue:true};
  1345. if (attr) config.attributeFilter=attr; // an array of attribute names.
  1346. attr_obs.observe(target, config);
  1347. function attrObserver(mutations) {
  1348. var results=mutations.filter(v=>{ return $(v.target).is(selectors)||$(v.target).is(jQcollection);});
  1349. if (results.length) { //Only send mutrecs together if they have the same target and attributeName.
  1350. let pos=0;
  1351. results.reduce((prev_res,curr,i)=>{ if ( prev_res.target!=curr.target || prev_res.attributeName != curr.attributeName) {
  1352. callback(results.slice(pos,i)); pos=i; } // not really a reduce!
  1353. return curr;
  1354. });
  1355. callback(results.slice(pos)); //////////////////<<<<<<<
  1356. } }
  1357. attr_obs.add=function(newmem) { jQcollection=jQcollection.add(newmem); return jQcollection.length; };
  1358. return attr_obs;
  1359. }
  1360.  
  1361. //
  1362. // Internal functions:
  1363. function nodeMutation(target, selectors, callback, type, include_subnodes) { //type new ones, 1, removed, 2 or both, 3.
  1364. var node_obs=new MutationObserver(mutantNodesObserver);
  1365. var jQcollection, cnt=0;
  1366. node_obs.observe(target, { subtree: true, childList: true } );
  1367. return node_obs;
  1368. function mutantNodesObserver(mutations) {
  1369. var sel_find, muts, node;
  1370. jQcollection=$(selectors);
  1371. for(var i=0; i<mutations.length; i++) {
  1372. if (type!=2) testNodes(mutations[i].addedNodes, mutations[i].target); // target is node whose children changed
  1373. if (type!=1) testNodes(mutations[i].removedNodes, mutations[i].target,"rmed"); // no longer in DOM.
  1374. }
  1375. function testNodes(nodes, ancestor, rmed) { //non jQ use, document.querySelectorAll()
  1376. if (nodes.length==0) return;
  1377. var results=[], subresults=$();
  1378. for (var j=0,node; node=nodes[j], j<nodes.length;j++) {
  1379. if (node.nodeType!=1) continue;
  1380. if (jQcollection.is(node)) results.push(node);
  1381. if (include_subnodes) subresults=subresults.add($(node).find(jQcollection));
  1382. }
  1383. results=results.concat(subresults.toArray());
  1384. if (results.length) callback(results, ancestor, rmed);
  1385. } //testNodes()
  1386. };
  1387. }
  1388. //
  1389. // End MutationObserver functions. Usage example, var obs=nodeInsertedListener(document,"div.results", myCBfunc); function myCBfunc(foundArrayOfNodes, ancestorOfMutation);
  1390. //
  1391.  
  1392. 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.
  1393. var len=objs.length,cnt=0,i,random_element=3;
  1394. if (!len) return false;
  1395. var maxi=objs.length-1, subinterval=interval/noOf_subintervals,
  1396. init_int=parseInt(objs[0].style[css_attr]), // assume same initital position and same units/suffix for all objs.
  1397. m=(target_val-init_int)/noOf_subintervals,
  1398. 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,
  1399. plotvals=new Uint32Array(noOf_subintervals).map(linear);
  1400. //console.log("manimate() targets:",objs," requestAnimationFrame:",css_attr,"currval:",objs.css(css_attr),target_val,interval,noOf_subintervals,CB,objs,"plotvals:",plotvals);
  1401.  
  1402. subinterval+=random(-subinterval/random_element,subinterval/random_element); /// Random element +/- 1/random_element.
  1403. if (delay) setTimeout(x=>i=setInterval(eppursimuove,subinterval,objs),delay);
  1404. else i=setInterval(eppursimuove,subinterval,objs);
  1405. function eppursimuove(that,b,c) {try{
  1406. requestAnimationFrame(tstamp=>objs.css(css_attr,plotvals[cnt]+suffix));
  1407. if (++cnt==noOf_subintervals) {
  1408. clearInterval(i);
  1409. CB && CB.call(that[1]);
  1410. }
  1411. } catch(e){log("WebEraser eppursimuove(), error@",Elineno(e),e);}}
  1412. } //manimate()
  1413.  
  1414. async function regcmds(){
  1415. var reg_args;
  1416. if(!regcmds.done) {
  1417. reg_args=["Erase Web Elements ["+(elems_to_be_hid?"some erased":"none erased")+"]", eraseElementsCmd,"","", "E"];
  1418. if(!GM_registerMenuCommand(...reg_args)) // from GM4_registerMenuCommand_Submenu_JS_Module, if there, undefined, else from gm4-polyfill which returns the menuitem DOM object.
  1419. GM.registerMenuCommand(...reg_args); // from gm4-polyfill. It sets body contextmenu style menu.
  1420. var ctrl_e_ON=await getValue("eraseElems_ctrlE",false);
  1421. if(ctrl_e_ON) $(window).keypress(function(e) {
  1422. if (!e.ctrlKey || e.key!="e") return;
  1423. setTimeout(function(){salert("Invoking web erasure function.");},300);
  1424. inner_eraseElements("fromCtrlE");
  1425. });
  1426. reg_args=["Set ctrl-e to invoke WebEraser now"+(ctrl_e_ON ? "[on]" : "[off]"), function(){
  1427. ctrl_e_ON^=true;
  1428. setValue("eraseElems_ctrlE",ctrl_e_ON);
  1429. 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 "
  1430. +"it may delay this erasure, ctrl-E can be typed to invoke the erasure at any time when "
  1431. +"ctrl-e function is enabled. Select again from menu to toggle");
  1432. },"","", ""];
  1433. if(!GM_registerMenuCommand(...reg_args))
  1434. GM.registerMenuCommand(...reg_args);
  1435. regcmds.done=1;
  1436. }// endif ! regcmds.done
  1437. if (regcmds.done==2 || $(".Web-Eraser-ed").length == 0) return;
  1438. reg_args=["Clear All WebErasures here on page & site; reloads page.",async function(){
  1439. await deleteValue(website+":erasedElems");
  1440. await deleteValue(webpage+":erasedElems");
  1441. location=location;
  1442. }];
  1443. if(!GM_registerMenuCommand(...reg_args))
  1444. GM.registerMenuCommand(...reg_args);
  1445. regcmds.done=2;
  1446. } // regcmds()
  1447.  
  1448. function setValue(n,v) {
  1449. if (!v && GM.deleteValue) return GM.deleteValue(n);
  1450. else return GM.setValue(n,JSON.stringify(v));
  1451. }
  1452. async function getValue(n,v) { var r1,res=await GM.getValue(n,JSON.stringify(v)); try {
  1453. r1=JSON.parse(res); return r1; } catch(e) { console.log("Error in parse of res:"+res+".Value:"+v+". Error:",e); return v; } }
  1454.  
  1455. function random(min,max) {
  1456. return Math.floor(Math.random() * ((max+1) - min)) + min;
  1457. }
  1458.  
  1459. function timer() { //console.time() and console.timeEnd() not working at the mo, so tstamp sent with each console.log
  1460. // Use with eg, time("start"); ... time("end"); // Each call to time() for from start and from previous call to time().
  1461. //if (window!=window.parent || timer.log) return;
  1462. if (timer.log) return; // aleady started
  1463. var originalLogger = console.log;
  1464. timer.log=originalLogger;
  1465. console.log = function () {
  1466. if (!timer.begin) {
  1467. timer.begin=Date.now();
  1468. timer.last_time=timer.begin;
  1469. originalLogger.call(timer.begin,">>>>Init timer "+location.pathname+":");
  1470. }
  1471. var args=Array.from(arguments);
  1472. var tstamp=Date.now();
  1473. var sdiff=tstamp-timer.begin, ldiff=tstamp-timer.last_time;
  1474. args.unshift(sdiff+"ms, "+ldiff+"ms\t");
  1475. timer.last_time=tstamp;
  1476.  
  1477. originalLogger.apply(this, args);
  1478. };
  1479. }
  1480.  
  1481. function logger2() {
  1482. var originalLogger = console.log;
  1483. logger2.log=originalLogger;
  1484. console.log = function () {
  1485. //alert(Array.from(arguments));
  1486. var roll="";
  1487. for (var i=0;i<arguments.length;i++)
  1488. roll+=arguments[i]+" ";
  1489. document.body.innerHTML+=roll+"<br>";
  1490. //originalLogger.apply(this, arguments);
  1491. };
  1492. // var originalInfo = console.info;
  1493. // logger2.info=originalInfo;
  1494. // console.info = function () {
  1495. // //alert(Array.from(arguments));
  1496. // document.body.innerHTML+=Array.from(arguments);
  1497. // originalInfo.apply(this, arguments);
  1498. // };
  1499. // var originalError = console.error;
  1500. // logger2.error=originalError;
  1501. // console.error = function () {
  1502. // //alert(Array.from(arguments));
  1503. // document.body.innerHTML+=Array.from(arguments);
  1504. // originalError.apply(this, arguments);
  1505. // };
  1506. }
  1507.  
  1508. function logger() {
  1509. $(document).dblclick(outputlogger);
  1510. var originalLogger = console.log;
  1511. logger.log=originalLogger;
  1512. console.log = function () {
  1513. if (!logger.this) logger.this=this;
  1514. // Do your custom logging logic
  1515. var argq=$(document).data("loggerq");
  1516. var args=Array.from(arguments);
  1517. if (!argq) argq=[];
  1518. if (document.readyState!=logger.state) {
  1519. argq.push(document.readyState+":");
  1520. logger.state=document.readyState;
  1521. }
  1522. argq.push(args);
  1523. $(document).data("loggerq",argq);
  1524. args.push(document.readyState);
  1525. originalLogger.apply(this, args);
  1526. };
  1527. } //logger()
  1528.  
  1529. function csscmp(prevval, newval) {try{
  1530. var that=arguments.callee;
  1531. var covered={}, roll="";
  1532. for (let i in prevval) {
  1533. covered[i]=1;
  1534. if (newval[i]===undefined) roll+="Removed: "+i+"="+prevval[i]+" ";
  1535. else if (prevval[i]!=newval[i]) roll+="Changed: "+prevval[i]+" to: "+newval[i]+" ";
  1536. }
  1537. for (let i in newval) if (!covered[i]) roll+="Added: "+i+"="+newval[i]+" ";
  1538. return roll||"Same";
  1539. }catch(e) {console.error("csscmp Error",e.lineNumber,e);}}
  1540.  
  1541. function nodeInfo(node1,plevel,...nodes) { // show DOM node info or if name/value object list name=value
  1542. //console.log("nodeInfo stack:",logStack());
  1543. if (node1==undefined || node1.length==0) return;
  1544. plevel=plevel||1;
  1545. if (isNaN(plevel) && plevel) { nodes.unshift(node1,plevel); plevel=1; }
  1546. else nodes.unshift(node1);
  1547. plevel--;
  1548. return nodes.map(node=> {
  1549. if (!node || typeof node=="string") return node;
  1550. if (node && node.attr) node=node[0];
  1551. if (node && node.appendChild) {
  1552. let classn=node.className ? node.className.replace("Web-Eraser-ed","") : "";
  1553. return node ? node.tagName.toLowerCase() + classn.replace(/^\b|\s+(?=\w+)/gi, ".").trim() + (node.id||"").replace(/^\s*\b\s*/,"#")
  1554. + (plevel>0 ? "<" + nodeInfo(node.parentNode,plevel):"")
  1555. : "<empty>";
  1556. }
  1557. else if (node && node.cssText) return node.cssText;
  1558. else
  1559. return ""+Object.entries(node) // entries => array of 2 member arrays [[member name,value]...]
  1560. .filter(x=> isNaN(x[0]) && x[1] ) //Only name value members of object converted to string.
  1561. .map(x=>x[0]+":"+x[1]).join(", ");
  1562. }).join(" ");
  1563. }
  1564. //selector(node,node.parentNode,0,0,"Web-Eraser-ed").replace(/^html>body>/,""); }
  1565.  
  1566. function outputlogger() {
  1567. var originalLogger=logger.log;
  1568. var that=logger.this;
  1569. var argq=$(document).data("loggerq");
  1570. originalLogger.call(that,"===============Logger Output==========================");
  1571. argq.forEach(function(v){
  1572. originalLogger.call(that,v); //this changes in forEach in this case!
  1573. }); // originalLogger.apply(this,argq);
  1574. originalLogger.call(that,"===============End Logger Output=======================");
  1575. return false;
  1576. };
  1577.  
  1578. function logStack(fileToo) { // deepest first.
  1579. var res="", e=new Error;
  1580. var s=e.stack.split("\n");
  1581. if (fileToo) res="Stack of callers:\n\t\t"; //+s[1].split("@")[0]+"():\n\t\t"
  1582. for (var i=1;i<s.length-1;i++)
  1583. res+=s[i].split("@")[0]+"() "+s[i].split(":").slice(-2)+"\n";
  1584. return !fileToo ? res : {Stack:s[0]+"\n"+res};
  1585. }
  1586.  
  1587. function Ppositions(el, incl_self,not_pos_break="") {
  1588. el=$(el); var roll="\n\n";
  1589. var els=el.parents();
  1590. if (incl_self) els=els.add(el).reverse();
  1591. els.each(function(){
  1592. var pos=$(this).css("position");
  1593. roll+=this.tagName+" "+pos+"\n";
  1594. if (! pos.includes(not_pos_break)) return false;
  1595. // /^((?!relative).)*$/ matches any string, or line w/o \n, not containing the str "relative"
  1596. });
  1597. return roll;
  1598. }
  1599.  
  1600. function ttimer(stage) {
  1601. return; //!! for profiling only.
  1602. if(window==window.parent) {
  1603. console.time("----from "+stage);
  1604. if(ttimer.last_stage) console.timeEnd(ttimer.last_stage); // print to console: "tTimer[n]: [num]ms"
  1605.  
  1606. if(ttimer.last_stage) console.log("\t----to "+stage);
  1607. ttimer.last_stage="----from "+stage;
  1608. }
  1609. }
  1610.  
  1611. 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.
  1612. var that=escapeCatch;
  1613. if(!that.flist || that.flist.length==0) {
  1614. that.flist=[cbfunc];
  1615. window.addEventListener("keydown", subfunc, true);
  1616. function subfunc(e) {
  1617. if (e.which == 27) {
  1618. console.log("escape",perm,"this is:",this);
  1619. that.flist.forEach(func=>func());
  1620. if(perm) return;
  1621. window.removeEventListener("keydown", subfunc, true);
  1622. that.flist=[];
  1623. }
  1624. };
  1625. } else
  1626. if(cbfunc) { that.flist.push(cbfunc); return; }
  1627. }
  1628.  
  1629.  
  1630. function summarize(longstr, max=160) {
  1631. longstr=longstr.toString();
  1632. if (longstr.length<=max) return longstr;
  1633. max=(max-3)/2;
  1634. var begin=longstr.substr(0,max);
  1635. var end=longstr.substr(longstr.length-max,max);
  1636. return begin+" ...●●●●... "+end;
  1637. }
  1638.  
  1639. function jqueryui_dialog_css() {
  1640. return ".ui-dialog-content,.ui-dialog,.ui-dialog textarea { font-size: 12px; font-family: Arial,Helvetica,sans-serif; border: 1px solid #757575; "
  1641. +"background:whitesmoke; color:#335; padding:12px;margin:5px;} "
  1642. +".ui-dialog-buttonpane { width:94%; background:whitesmoke; font-size: 10px; cursor:move; border: 1px solid #ddd; overflow:hidden; } "
  1643. +".ui-dialog-buttonpane button { background: #f0f0e0; }"
  1644. +".ui-dialog-buttonset { float:right; } "
  1645. +".ui-widget-overlay { background: #aaaaaa none repeat scroll 0 0; opacity: 0.3;height: 100%; left: 0;position: fixed; top: 0; width: 100%;}"
  1646. +".ui-button,.ui-widget-content { text-align:left; color:#333; border: solid 1px #757575; padding: 6px 13px;margin: 4px 3px 4px 0;} "
  1647. +".ui-corner-all,.ui-dialog-buttonpane {border-bottom-left-radius:30px;}"
  1648. +".ui-button:hover { background-color: #ededed;color:#333; } "
  1649. +".ui-button { background-color: #f6f6f6; color:#333; }"
  1650. +".ui-dialog {position:absolute;padding:3px;outline:none;}"
  1651. +".ui-resizable-handle { position:absolute; cursor: url(data:image/svg+xml;base64,"
  1652. +"iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAAAAACo4kLRAAAACXBIWXMAAAsTAAALEwEAmpwYAAABAUlEQVQY022RsWrCYACEvz9EK1HQCilYu+jWdNKlj9BNN1/Cp/IBFHTMExihdTCIm0sR26W6KNHkvw6lunjLjffdnXn7fG0/PzzeG0A/m+/Ve/REB3CL/laStn7RBTpOG0iTXgWg0ktSoO20DJDv5gBy3TxgWkQFAG+QSdnAAyhErMtuFSiN0nRUAqpuec2x2V/4YOr7fd2AH/ebR+zhbMOaCWJrF4GphfZ8sEhSNm3EVrJxY5pJkhGAkjtzNRxuyAGws2Ap0DKYWYDbQRek3e6K9A8/TNPhBf5mzbEBvDCTpCz0ADN25gJOkzPAeXICNL85spu8/N0B8LDafK0+ouQXfemVYVtdIewAAAAASUVORK5CYII="
  1653. +") 10 10, row-resize; } .ui-resizable-sw {bottom:5px;left:5px;}"
  1654. +".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; }"
  1655. + (str=>str+str.replace(/-moz-/g,"-webkit-"))(
  1656. // ".sfswe-content :-moz-any(div,input) { font-size:13px;padding:6px;margin:4px 3px 4px 0;color:#333; opacity:1; }"
  1657. ".sfswe-content :-moz-any(div,input) { font-size:13px;padding:0px;margin: 0;color:#333; opacity:1; }" //background:whitesmoke;
  1658. +".sfswe-content :-moz-any(span) { font-size:13px;padding:0;margin:0;color:#333;}"
  1659. +".sfswe-content :-moz-any(a,a:visited) { color:#333;text-decoration:underline; padding:0;margin:0;}"
  1660. +".sfswe-content :-webkit-any(a,a:visited) { color:#333;text-decoration:underline; padding:0;margin:0;}"
  1661. ) +".sfswe-content a:hover {opacity:0.5;}"
  1662. +".ui-tooltip { font-size: 7px; }"
  1663. +".sfswe-ticks * {font-size:11px;padding:0px;margin:2px;}"
  1664. .replace(/\.ui/g,".sfswe-sprompt .ui"); //gives namespace of .sfswe-prompt
  1665. }
  1666.  
  1667. function environInit(environ) { // returns false if GM environment is there, otherwise it calls main when ready and immediately returns true.
  1668. 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);
  1669. if (/Chrome/.test(navigator.userAgent)) environ.plat_chrome=true;
  1670. environ.plat_mac = /^Mac/.test(navigator.platform);
  1671.  
  1672. try { environ.nonGMmode= (typeof GM == "undefined"); } // || "Barychelidae"!=GM_getValue("arachnoidal","Barychelidae"); }
  1673. catch(e) { environ.nonGMmode=true; }; //eg, chromium stadalone
  1674.  
  1675. //environ.nonjQ = !window.jQuery || parseFloat(jQuery.fn.jquery) < 3.1;
  1676. if (nonGMmode){ // chromium bare, ie, w/o tamper.
  1677. console.info("WebEraser userscript in non GM_ mode at "+location.href, "typeof GM:",typeof GM, "nonGMmode",nonGMmode);
  1678. environ.unsafeWindow=window;
  1679. environ.old_GM_getValue=environ.GM_getValue;
  1680. try { localStorage["anothervariable"]=32; } catch(e) {
  1681. window.nostorage=true;
  1682. if(!iframe) console.error("No local storage, no GM storage, use Tampermonkey to include this script on page:",location.href);
  1683. window.localStorage={};
  1684. }
  1685. //if(!window.nostorage) console.log("Have local storage",localStorage.anothervariable);
  1686. environ.GM_getValue=function(a,b) { return localStorage[a]||b; };
  1687. environ.GM_setValue=function(a,b) { localStorage[a]=b; };
  1688. environ.GM_getResourceURL=function(url) {
  1689. var ext="Dbl"; if (url.endsWith("Orig")) ext=".orig"; else if (url.endsWith("Xsm")) ext="ExSm"; else if (url.endsWith("Trpl")) ext="Trpl";
  1690. return "https://raw.githubusercontent.com/SloaneFox/imgstore/master/whiteCurtains"+ ext +".jpg";
  1691. };
  1692. //environ.GM_registerMenuCommand=x=>null;
  1693. environ.GM_addStyle=function(cssSheet) { $("head").append("<style>"+cssSheet+"</style>"); };
  1694. environ.uneval=function(x) { return "("+JSON.stringify(x)+")"; }; //Diff is that uneval brackets string and json excludes code only data allowed in json.
  1695. var xhr_queue=[], xhr=new XMLHttpRequest();
  1696. xhr.onload=x=> { //arrow function means this remains window not xhr (as a function would).
  1697. //console.log(xhr.responseURL,"onload to eval in window, jQuery in window? ",!!window.jQuery,!!window.$,!!this.jQuery);
  1698. var synop=(xhr.response||"").substr(0,40);
  1699. try {
  1700. 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); }
  1701. if (xhr_queue.length) { xhr.open('GET', xhr_queue.shift()); xhr.send(); }
  1702. else if (!iframe) main.call(window); //////////////////
  1703. };
  1704. xhr.onerror=e=> {
  1705. console.log("W/e XHR Error: "+e,", E:",e,"XHR:",xhr,"After error queue:",xhr_queue);
  1706. if (xhr_queue.length) xhr.open('GET', xhr_queue.shift()); xhr.send();
  1707. };
  1708. var jq_versions_prior={
  1709. core: parseFloat(environ.$ && environ.$.fn && environ.$.fn.jquery) || 0,
  1710. ui: parseFloat(environ.$ && environ.$.ui && environ.$.ui.version) || 0
  1711. };
  1712. if(jq_versions_prior.core < 1.7)
  1713. xhr_queue.push("https://code.jquery.com/jquery-1.7.2.js");
  1714. if(jq_versions_prior.ui < 1.12)
  1715. xhr_queue.push("https://code.jquery.com/ui/1.12.1/jquery-ui.js");
  1716. xhr_queue.push("https://raw.githubusercontent.com/SloaneFox/code/master/gm4-polyfill-1.0.1.js");
  1717. xhr_queue.push("https://raw.githubusercontent.com/SloaneFox/code/master/gm-popup-menus-1.3.7.js");
  1718. xhr_queue.push("https://raw.githubusercontent.com/SloaneFox/code/master/sfs-utils-0.1.5.js");
  1719. xhr.open('GET', xhr_queue.shift()); xhr.send();
  1720. return true;
  1721. } else return false; //if (nonGM || nonJQ)
  1722. }
  1723. ttimer("end globs setup");
  1724.