slow! / Simple Video Audio Blocker

// ==UserScript==
// @name Simple Video Audio Blocker
// @description Block loading of videos/audio (flash or html5) at the site being visited.  Click to enable once, alt-c to enable for site permanently.
// @include *
// @version 1.6.4
// @updateURL https://openuserjs.org/meta/slow!/Simple_Video_Audio_Blocker.meta.js
// @run-at   document-start
// @require  https://code.jquery.com/jquery-latest.js
// @require  https://openuserjs.org/src/libs/slow!/GM_registerMenuCommand_Submenu_JS_Module.js
// @grant GM_registerMenuCommand
// @grant GM_log
// @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// ==/UserScript==

// @-version Apr 2017.  Youtube changes updated. v1.6.4.
// @-version May 2016.  Some advert videos need further work.
// @-version Dec 2015.  Css in head interferred with now by youtube and so losing track of element.  Youtube click wasn't playing.
// @-version July 2015.  HTML5, prevent buffering of it while blocked.
// @-version March 2013.  Bug at google groups, for iframes when not blocking them.
// @-version February 2013.  Updated to prevent autostart of sounds at new yuoutube site.
// @-version January 2010.  Updated for Firefox 4.
// @-version October 2010.    Google Chrome platform adaptation.
// @-version August 2010.  GM menu option added to also block frames (webpages within webpages that are often used for adverts.)
// @-version May 2010.  Handle dynamically created flash objects.
// @-version Simple Flash Blocker
// try {
// (function() {

//unsafeWindow.markT("Begin FlashB");

GM_platform_wrapper ("Simple Video Audio Blocker", 4, registerMenus);  // to also handle Google Chrome.

var GM2_log=function(t) {GM_log(t);console.info(t);};

var log=function(t) {
    console.log.apply(console,arguments); //Was getting implement interface error, pass console instead of this.
};

function logcaller() {
    var res="", e=new Error;
    var s=e.stack.split("\n");
    console.log(s[0]);
    res="Stack of callers:\n\t\t"; //+s[1].split("@")[0]+"():\n\t\t";
    for (var i=1;i<s.length-1;i++)
	res+=s[i].split("@")[0]+"(), line,pos:"+s[i].split(":").slice(-2)+"\n\t\t";
    console.log(res);
    //    console.trace();
}

// var old_onbu=window.onbeforeunload;
// window.onbeforeunload=function() {
//     console.log("onbeforeunload called");
// };
// var old_addEventListener=window.addEventListener;
// window.addEventListener=function(){
//     console.log();
// }


function logcut(t) { t=t.substring(0,80);log(t);}

log=function(){};

//log("simple_video_blocker @"+location.host+", frame: "+iframe());
//if (iframe()) return; //!!
var block_frames=GM_getValue("block_frames", false), frames_css="", image_css="";
var  val="b"; try { 
    val = GM_getValue(location.host, "b"); } catch(e) {GM2_log("No hostname "+e); return; }
var block_subpages=GM_getValue("block_subpages", false);
//extensions.greasemonkey.scriptvals.userscripts.org/Simple Flash Blocker.block_subpages
var block_subsubpages=GM_getValue("block_subSubpages", false);
var subpage=location.host+location.pathname;
if (block_subsubpages) subpage=location.href;
if (block_subpages && val!="b" ) 
    val = GM_getValue(subpage, "b");

log((block_subpages?" subpage ":"")+"video blocker.  Site blocking val:"+val+", blocking frames:"+GM_getValue("always_block_frames"));
var types="object, embed, video"+(val=="i" || val=="e" ? ", img": "");
var youtube=/youtube/.test(location) ;
if (block_frames) {
    frames_css="frame, ifame,";
    types+=", iframe, frame";
}
if (val=="i" || val=="e")
    image_css="img,";

var initial_css=image_css+frames_css+"object, embed, .fbus_edObj { display: none ! important; }"; 
GM_addStyle(initial_css);

var head=document.getElementsByTagName("head")[0];
var css_elem=head?head.lastElementChild:{}, clear_styles;
String.prototype.trim = function () {    return this.replace(/^\s*|\s*$/g,""); };
css_elem.id="fbus-css";
var final_css=".fbus_edObjFree *, fbus_edObjFree { display: inline-block ! important; } .fbus_edObj *, .fbus_edObj { display: none ! important; }"; 

var display_inline=types+", .fbus_edObj { display: inline-block ! important; }"; 
var img="data:image/gif;base64,R0lGODlhFgAWAOfQAAABAAACAAAHCg4HBRoeICEjICIkITJETj1CRDZESTNFTzRGUEFFRzlHTDZIUjdJUzhKVDpLVjtNV0BOUz1PWUlNTz5QWj9RW0FTXUZUWkNVX0RWYE9UVkRXYUpYXkdZZExbYU1cYktdaE9eY05ga09hbFJhZlNiZ01jc1FjbVRjaFZkalRncVNqeltpb2NoaltqcE9tfFFvfl5tc1dufmdrblxueV1velRygWRzeV10hGV0el91hmZ1e2B2h2d2fHB0d1p5iFt6iWZ4g2p5f2h6hWx7gXB/hXGAhnh/h3KBh3uAg3qBiXSDiXGEj3WEinaFi3CGl3eGjHuKkH6KioKJkX2MknqNmXuOmn+OlICPlYGQloOSmIaSk4STmYWUmoaVm4eWnIiXnYSYo4mYnpKXmYqZoIaapYuaoYibpoyboo2co5Obo46epI+fpZCgppGhp5WhopOjqZSkqpilpZOmspamrJenrZqqsJursZyssp2ttKOstJ6utZ+vtqGwt6KxuKOzuaqyuqS0uqW1u66zta21vae3vbG2uK+3v6m5v7C4wKq6wLO4u7G5wq+8va29xLa7vrO8xK6+xbG+v7W+xrTAwba/x7m/wbnBybzBw73CxLXFy77Dxb/ExrfHzcDFyLjIzsLHysPIy8DJ0cTJzMXKzcTN1cfNz8bO1snO0MrP0cjQ2MvQ0snR2szR1MrS283S1c7T1s/U19DV2NHX2dLY2tTZ29Xa3Nbb3dfc39jd4Nne4drf4tvg49zi5N/k5uDl6OPo6+Tp7OXq7ebs7uft7+nu8Ovw8+7w7ezx9PL08fP18vD2+Pb49Pf59v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAP8ALAAAAAAWABYAAAj+AP8JHCgQVy1WqUJNIsiQ4a1dwYYVE8aLlaI6DQmykjXMmbNlypAZKwbs05goGTmxUvZMmTFhwHzx2qWrFyksQRgqIuWM2TFhzXjdqiXrVatVqzo58UEQEDFmxoLx4mDrVyxVpkSB8qQJD4+BaVI5KybVVwADhWppzVQpUiJBWGgIPHOMWDBfuHYFCDBgCS1PlhYZGqTHzY1/QvIku4trlq29eytMqkQIkJ47bIaw0HGImK9ctV7Jgry3AB1IevDIEVMkBY5JwHLRioVqFem9AmromdOGyxASMgzxovVKlShTt/cy0POGjJUcImLImdUK1ahOoJIDCfRGDZcnLj6ytLhSapTWS5lIE6jyp42aMFOMmOjwzwYiTJssPZIEGcEdPG2QAUYWSsxA3z8oWNHIIoscwsheL+wWYBhbSLHDCBcMlIIYfPzxhx4BUEGIHGsIqAUUP6hAAUEisFBGHHbYwcQdcJTIhRVN7LCCBREwtEEJSXTxhRtheKHFFE0QMUMIEzyQEQYauGCEEkocYUQPM5yQAQQNZDSQBBZ4AMIIIGQwgQMJeNmQAw4soMABaWYUEAA7";
var arrow_count=0, frame, docclicks=[];
var initinterval, phase=0, icnt=0, mins, duration;
var arrowpos={};

if(val != "u")
    nodeInsertedListener(document,"video,object,embed,iframe",newNode);
//    window.addEventListener("DOMNodeInserted", newNode, false);


addEventListener("click",function(e){
    console.log("Click arrowpos listener, target:",e.target,"arrowpos",arrowpos);
    console.log( e.clientX>arrowpos.startX,e.clientX<arrowpos.endX);
    return;
    if ( ( e.clientX>arrowpos.startX && e.clientX<arrowpos.endX) 
	 && (e.clientY>arrowpos.startY && e.clientY<arrowpos.endY) ) {
	console.log(freeFlash);
	freeFlash(e,arrowpos.div);
	e.preventDefault(); e.stopPropagation();
    }
},true);

$(window).load(function(){log("---loaded---");});
$(main); //=== $(window).ready("main");

// catchSniffAjaxEvents();
// $.ajax({beforeSend: function(e,f){ console.log("JQ Global Ajax event",e,f);},
// 	complete: function(e,f){ console.log("Ajax Global event end, ",e,f);}
//     });

//
// Mainline
//

function main() {
    //log("MAIN "+(iframe()?"--iframe":"")); //see newNode() for other initiate.
    //logcaller();
    phase=1;
    if(val != "u") { //u==unblocked
	nodeRemovedListener(document,"object,embed,iframe,video",nodeRemoved);
	//addEventListener("DOMNodeRemoved", nodeRemoved, false);
	buttonUpFlash();
	FinalizeCss();
    } // end if !=u , not unblocked
    else { 
	if ((block_frames && val != "u" ) || GM_getValue("always_block_frames", false)) {
	    buttonUpFlash("frame, iframe");
	    FinalizeCss();
	}
	else {
	    GM_addStyle(display_inline);
	}
    }
    $(document).click(handleDocClick);
    $(document).on("keydown", handleKeyDown);
    phase=2;
}//main

function buttonUpFlash(tags, in_elem) {
    //log("buttonUpFlash extra tags to button-up:"+tags+"; "+in_elem);
    //logcaller();
    function invisible(elem){ return (getComputedStyle(elem, null)||{}).display=="none";  }
    var elem, objs, arrowed;
    if (! in_elem) {
	objs = getElementsByTagNames( ! tags ? types : tags );
	console.log("buttonUpFlash, Number of "+types+" elems: "+objs.length+". Or tags? "+tags);
    }
    else
	objs=[in_elem];
    for(var i=0; elem=objs[i];  i++) {
	//log("\tCheck each obj if already arrowed, obj: "+elem.tagName+", class: " +elem.className +", p class: "+elem.parentNode.className+".");
	if( ! /fbus_edObj(Free)?\b/.test(elem.className)
	    && ! /fbus_edObj\b/.test(elem.parentNode.className)
	    && ! /sfsnoflblock/.test(elem.className)
	    && ! ( /frame/i.test(elem.tagName)
		   && invisible(elem) )
	    && elem.id != "GM_config" ) {
		placeArrowImage(elem);
		arrowed=true;
	    }
	else log("\talready ok");
    }
    if (objs.length==0) tryDownloader();
    return arrowed;
}

function placeArrowImage(elem) {
    console.log(elem.tagName+", Place arrows on, id: "+elem.id+", class: "+elem.className+", type: "+elem.getAttribute("type"));
    var jelem=$(elem);
    if (jelem.data("freed")) { log("Was freed");elemPosClicked(elem);return; }
    //elem.setAttribute("autostart", "false");
    var div = document.createElement('div'), jdiv=$(div);
    var h=jelem.height(), w=jelem.height();
    var sh=jelem.parent().height(), sw=jelem.parent().width();
    //log("h, w, sh, sw: "+h+", "+w+", "+sh+", "+sw);
    if (sh>h) h=sh;
    if (sw>w) w=sw; //If NaN from parseInt all tests >, <, ==, yield false.
    if (h==0) h=jelem.parents().eq(1).height();
    if (w==0) h=jelem.parents().eq(1).width();
    //log("Final h, w: "+h+", "+w);
    var elem_area=h*w, win_area= $(window).height()*$(window).width();
    //log("areas, elem:"+elem_area+", win: "+win_area);
    if ( /frame/i.test(elem.tagName) &&  2*elem_area > win_area ) {
	//log("frame is the window! Returning");
	return;
    }
    var suffix="px";
    div.setAttribute("heightwidth", h+"-"+w+"-tag-"+elem.tagName);
    var sfac=0.98;
    div.setAttribute("style", "    z-index:999995 ! important;"
		     +"border:6px double silver;min-width:50px;min-height:50px;"
		     +"height:"+(h*sfac)+suffix
		     +";width:"+(w*sfac)+suffix
		     +";background:url("+img+")"
		     +";background-repeat: no-repeat;"
		     +"background-position: center; "
		     +"cursor:pointer;"
		     +"background-color: #381714;" ////#5c3317"
		     +"display: inline-block;"
		     +"position: absolute;"
		     //		     + ( /IMG/.test(elem.tagName) ? "float: left;" : "")
		    );
    getById("watch-sidebar").style.zIndex=1;
    var src_url=srcUrl(elem);
    get_duration(elem,div);
    div.title= "Click here to play "+( block_frames || val == "i" || val == "e" ? " or to reveal blocked content": "")
	+(src_url?", url: "+src_url+" (see console for copying url)" : "" ) + (elem.type?", type: "+elem.type:"");
    //    var d2=getSymbolDiv(src_url,"",h);
    var par=div;
    //   par.appendChild(d2);
    //src_url=srcUrl(elem, true);
    var src=elem.getAttribute("src");
    var src_elem=jelem.find("source");
    if (!src) src=src_elem.attr("src");
    if ( (!src || !src.startsWith("http")) && iframe()) src=location.href;
    console.log("src at get symbol",src," src_url:"+src_url+".", "Iframe",iframe());
    //    var [d2,dn_link]=getSymbolDiv(src_url||src||"", "Try download by clicking symbol here: ",h);
    var [d2,dn_link]=getSymbolDiv(src_url||src||"", "",h);
    par.appendChild(d2);
    par.className+=" linkAppended";
    div.className = "fbus_arxrow";

    div.dataset.src = src; //dataset special DOM store for strings only. see $.data() to store any type
    div.dataset.data = elem.getAttribute("data");
    elem.setAttribute("orsrcSFB", src);
    if (elem.pause) {
	console.log("elem has pause",elem);
	if (!src) {
	    //$(elem).data("freed",true); //well behaved wsa clicked.
	    console.log("PlaceArrows().  No src, pause video & wait for src change.",elem,".");
	    elem.pause();
	    setTimeout(function(){elem.pause();},500);
	    attrModifiedListener(elem,["src"],function(mutrec){
		var src= srcUrl(elem);
		console.log("AttrModifiedListener src:",src,"elem.src",elem.getAttribute("src"),"orsrcSFB:",elem.getAttribute("orsrcSFB"));
		if (!src) src=elem.getAttribute("orsrcSFB");
		d2.title="url for video may be downloadable, this is a link that clicks to: "
		    +src;
		dn_link.href=src;
		if (mutrec.attributeName=="src") placeArrowImage(mutrec.target);
				 
		}); // newNode should then call buttonUpFlash->placeArrowImage()
	    // elem.addEventListener("DOMAttrModified", function(e){   // ditto
	    // 	log("Attribute of"+e.target+", "+e.attrName+" modified from:"+e.prevValue+" to new/current:"+e.newValue);
	    // 	if (e.attrName=="src" && e.prevValue=="" && e.newValue) { ///video/i.test(e.target.tagName) && e.target.getAttribute("src")) {
	    // 	    placeArrowImage(e.target);
	    // 	}
	    // });
	    return;  //wait for source to be set.
	} //!src
	console.log("Pausing video with source "+src.substr(0,40),elem);
	elem.pause();
	var vlength=$("#player").find("span").eq(2).text();
	//console.log("Get len.");
	//setTimeout(getVidLength,5000,src);
	// $("body").prepend("<img id=vidasimg src='"+img+"' title='Get "+src
	// 		  +"' style='border: 20px dotted red;position:fixed;z-index:9999999;'>");
	// console.log("Video 0 is",elem);
	// $("#vidasimg").click(function(){
	//     console.log("Click ",src);
	//     //getVidLength(src);
	//     var fname=location.search.replace(/.+?=/,"")+".mp4";
	//     jqget(src,fname);
	//     return false;
	// });
    } // endif elem.pause
    //else elem.setAttribute("src",""); 
    //elem.setAttribute("src",""); // avoiding buffering/autostart of audio //yt cant handle this.
    //src_elem.attr("src","");

    elem.setAttribute("data","http://dev.null"); // avoiding autostart of audio
    if (elem.seekable) pauseAtEnd(elem, "placeArrowImage");
    arrow_count++;
    div.id="fbusx";
    //div.addEventListener("click", function(e) {
    console.log("add click");
    jdiv.click(function(e){
	console.log("div click, call freeFlash");
	return freeFlash(e, this, elem); 
    });
    var type=elem.getAttribute("type");
    if (!type) type=src_elem.attr("type");
    if ( /frame/i.test(elem.tagName) && ( ! /shockwave-flash/.test(type) ) )
	if ( ! block_frames )  {
	    log("Dont block frames, ret");	
	    elem.dataset.src=div.dataset.src;
	    elem.dataset.data=div.dataset.data;
	    elem.dataset.sfb="freeframe";
	    console.log("ret");
	    return;
	}

    elem.parentNode.insertBefore(div, elem);
    jdiv.data("elem", elem);  //cant use dataset
    jelem.addClass("fbus_edObj"); //display:none
    
    log("Inserting sibling to cover video, add fbus_edObj to class of "+elem);
    jelem.parent().addBack().css({"z-index":999991,opacity:1});
    jdiv.css("z-index",2147483647);
    var pos=jdiv.position();
    pos.left+=jdiv.width()/2|0;
    pos.top+=jdiv.height()/2|0;
    var elatpt=$(document.elementFromPoint(pos.left, pos.top)); //x,y
    console.log("elatpt",elatpt,pos,jdiv.width(),jdiv.height());
    
    if (jdiv.is(elatpt)) return;
    
    var intersection=jdiv.parents().has(elatpt.parents()).eq(0); //parents() is ordered nearest -to- furthest.
    console.log("intersect is:",intersection, "[0]",intersection);
    elatpt.parentsUntil(intersection).each(function(){ //parentUntil(), up to but not including.
	var trbl=$(this).css(["top","right","bottom","left"]);
	if (stringify(trbl)==stringify({top:"0px",right:"0px",bottom:"0px",left:"0px"})) {
	//    $(this).css("right","auto");
	    console.log("Zeroes!!",$(this).css("right"));
	}
	console.log("trbl:",nodeInfo(trbl)," of ",$(this));
    });
    //    if (trbl==)
    // console.log("jdiv is not ptel obj setup arrowpos");
    // arrowpos.startX=pos.left;    arrowpos.endX=pos.left + jdiv.width();
    // arrowpos.startY=pos.top;     arrowpos.endY=pos.top + jdiv.height();
    // arrowpos.div=div;
    //ptel.parent().addBack().css("z-index",-9);
}; //placeArrowImage()

function freeFlash(e, div, elem) { // arrow click event
    //logcaller();
    elem=$(div).data("elem");
    console.log(  "freeFlash(): "+val+". e(target): "+e+", elem "+(elem?elem.tagName:null),"elem.pause",elem.pause  );
    display(div, elem);
    if ( e && e.target.id=="dnlink") {   //  e.stopPropagation();     e.preventDefault(); 
	// log("Download should begin on: "+e.target.href);
	return true; }
    if (elem.pause) {
	var src_elem=$(elem).find("source");
	src_elem.attr("src",div.dataset.src);
	$(elem.parentNode).css("z-Index","0");
	elem.currentTime=0;
	elem.muted=false;
	console.log("freeFlash() currentTime ",elem.currentTime," muted ",elem.muted);
	elem.play();
    }
    else {
	elem.setAttribute("src", div.dataset.src);
	$(elem).find("source").attr("src",div.dataset.src);
	elem.setAttribute("data", div.dataset.data);
	console.log("freeFlash(), reset src data atts to "+div.dataset.src+" "+div.dataset.data+" resp.");
    }

    objs = getElementsByTagNames( types, elem);
    for(var i=0; elem=objs[i];  i++) {
	objs[i].style.display='inline';
	objs[i].className+=" fbus_edObjFree";
	console.log("freeFlash(), adding in freeFlash fbus_edObjFree "+objs[i]);
    }
    //    if (val=="i" || val=="e" && elem.tagName == "IMG") {
    if (e) {
	e.preventDefault();
	e.stopPropagation();
    }
    //}
} // freeFlash()

function newNode(nodes) { //DOMNodeInserted || src mod
    console.log("newNode()",nodes);
    nodes.forEach(function(e){
	e={target:e};
	if (e.target.seekable) { // .seekable => <video>
	    console.log("newNode "+e.target.tagName+", class: "+e.target.className); //e.prevValue e.newValue e.attrName
	    var v=e.target;
	    elemPosClicked(v);
	    v.muted=true;
	    v.addEventListener("loadstart", function(e){
		if (!elemPosClicked(this))
		    if (buttonUpFlash("",this)) pauseAtEnd(this, "loadstart");});
	    v.addEventListener("loadeddata", function(e){
		if (!elemPosClicked(this)) {
		    if (buttonUpFlash("",this) || $(this).hasClass("fbus_edObj"))
			pauseAtEnd(this, "loadeddata");
		}
	    }); 
	}//if seekable		  
	if ( phase==2 && /^(OBJECT|EMBED|IFRAME)$/.test(e.target.tagName)
	     && ! /fbus_edObjFree/.test(e.target.className) )
	    if (!elemPosClicked(e.target))
		buttonUpFlash();
    });
} // newNode()

function nodeRemoved(nodes) {
    nodes.forEach(function(e){
	e={target:e};
	//if (/^(OBJECT|EMBED|IFRAME|VIDEO)$/.test(e.target.tagName))  {
	//log("NodeRemoved "+e.target.tagName);
	var prev=e.target.previousElementSibling;
	if (prev && /fbusx/.test(prev.id) )
	    prev.parentNode.removeChild(prev);
	//}
    });
}

function display(d, obj) {
    console.log("display d:",d,"obj",obj,"d.p",d.parentNodes);
    if ( /fbus_/.test(d.className) && ! /^(OBJECT|EMBED|IFRAME)$/.test(d.tagName) )
	if (d.parentNode) d.parentNode.removeChild(d);
    obj.style.setProperty('display', 'inline', 'important');
    obj.className=obj.className.replace(/fbus_edObj\b/,"fbus_edObjFree");
    d.className=d.className.replace(/fbus_edObj\b/,"fbus_edObjFree");
    //log("Restored classnames to "+obj.className+" "+d.className+", to rm: "+d.tagName);
    if (obj.nextElementSibling &&
	( /^fbusx/.test(obj.nextElementSibling.id)
	      || /^fbus_edObj/.test(obj.nextElementSibling.className) ))
	display(obj.nextElementSibling, obj.nextElementSibling.nextElementSibling);
    if (obj.previousElementSibling && /fbus_edObj\b/.test(obj.previousElementSibling.className) && obj.previousElementSibling.previousElementSibling)
	display(obj.previousElementSibling.previousElementSibling, obj.previousElementSibling);
}

function elemPosClicked(e) {
    e=$(e);
    var pos=e.position();
    var h=e.height(), w=e.width();
    //log(docclicks.length+"-Position of "+e[0]+" is top:"+pos.top+" ("+h+"h), left:"+pos.left+" ("+w+")");
    for (var i=0;i<docclicks.length;i++) {
	var x=docclicks[i].x, y= docclicks[i].y;
	var clicked_elem = document.elementFromPoint(x,y); 
	if (e[0] == clicked_elem) 
	    return true;
    }
}

function pauseAtEnd(v, from) {
    console.log("pauseAtEnd",v,v.parentNode,Number(v.duration));
    v.muted=true;
    if (Number(v.duration)) {
	v.pause();
	v.currentTime=v.seekable.end(0)|0;
	if(youtube) v.currentTime=0;
	v.pause();
	console.log("HTML5 video sourced.  Vid Length "+v.duration+"secs, mins:"+mins+", p: "+v.currentTime+" "+from);
	return true;
    } else setTimeout(x=>pauseAtEnd(v,from),5000);
}

function get_duration(v,div){
    setTimeout(function(){
	if (v.play) {v.play();v.pause();}
	if (Number(v.duration)) {
	    duration=v.duration; //seconds
	    mins=(duration/60)|0;
	    if (mins==0) mins=duration/60;
	    div.title+=(mins?".  "+mins+" mins.":"");
	    //console.log("v.duration ",v.duration," play:",div.title,mins);
	}
	
    },4000);
}

//setInterval(function(){console.log("activeElement is:",document.activeElement);},5000);

function FinalizeCss() {
    if (clear_styles) return;
    $("#fbus-css").remove();
    GM_addStyle(final_css);
    head.lastElementChild.id="fbus-css-some";
}

function handleKeyDown(e) {
    //log("simple_video_blocker key "+e.keyCode); //alt-c to clear site
    if(e.keyCode==27) {  //escape
	GM_addStyle(display_inline);
	delArrows();
    }

    if(e.keyCode == 67 && e.altKey && ! (e.ctrlKey || e.shiftKey || e.metaKey) ) { //((e.shiftKey && e.ctrlKey) != e.altKey)) {
	toggleBlocking();
	e.stopPropagation();
	e.preventDefault();
	// this.addEventListener('keyup', function(e) {
	//     e.stopPropagation();
	//     e.preventDefault();
	//}, false);
	return true;
    }
}

function handleDocClick(e) {
    if (e.button!=0) return;
    var t=e.target;
    log("Doc CLICK "+ t.tagName+" "+t.className+" "+t.id+" e.pageX:"+e.pageX+", "+e.pageY);
    var elem=document.elementFromPoint(e.pageX,e.pageY);
    docclicks.push({x:e.pageX,y:e.pageY});
    var neighbours=t.parentNode&&t.parentNode.getElementsByClassName("fbus_arxrow");
    if (neighbours && neighbours.length) { 
	var n=neighbours[0];
	setTimeout(function(){
	    freeFlash(e, n, n.nextElementSibling); }, 0);
    }
    //return false;
}


//
// Functions.
//
function toggleBlocking() {
    val=GM_getValue(location.host, "")[0]; // u, i or b
    if (block_subpages && val != 'b') val=GM_getValue(subpage, "")[0]
    //log("toggle");
    if(val != 'u'){
	if (val != "i" ) {
	    alert("Clearing video blocking of: "+(!block_subpages?location.host:subpage));
	    GM_addStyle(display_inline);
	    delArrows();
	    if ( ! block_subpages)  GM_setValue(location.host, 'u');
	    else { GM_setValue(subpage, 'u'); GM_setValue(location.host, 'u');}  // "u", explicitly unblocked
	}
    } // end if !=u
    else{
	//var reply=confirm("Blocked site "+location.host+" for Flash.\n\nClick 'Cancel' or escape, to also block images at this site");
	alert("Blocked site "+(!block_subpages?location.host:" webpage "+subpage)+" for Flash.");
	//	if ( reply) {
	GM_addStyle(initial_css);
	buttonUpFlash();
	if ( ! block_subpages)	    GM_setValue(location.host, "");
	else                          GM_setValue(subpage, "");
    }
}

function getSymbolDiv(src_url, msg, h) {
    var d2=document.createElement("div");
    var dn_link=document.createElement("a");
    var txt=document.createElement("span");
    d2.appendChild(txt);
    d2.appendChild(dn_link);
    dn_link.href=src_url.replace(/^.*?(?=http[s]?:\/\/)/,""); dn_link.id="dnlink";
    //dn_link.textContent=

    txt.style.cursor="default";
    with (d2.style) { position="relative";     top="70%";       }
    if (h > 200)
	//	txt.textContent=msg||"Try download by clicking this symbol: ";
		txt.textContent=msg||"";
    else       {
	d2.style.left="-10%";
	d2.style.top="20%";
    }
    dn_link.textContent="For download, try click symbol here:"+"...  \u2720";     // \u2720 is: ✠

    txt.style.fontSize="150%"; 
    dn_link.style.fontSize="250%"; 
    d2.title=msg+src_url;
    //+(src_url?src_url: elem.getAttribute("orsrcSFB"));
    return [d2,dn_link];
} //end getSymbolDiv()

function getElementByName(parent, name) {
    var elems=parent.getElementsByTagName("*");
    for (var i=0; i < elems.length; i++) {
	//log("cmp "+elems[i].name+" == "+name);
	if (elems[i].name==name) return elems[i];
    }
}

function delArrows(){
    var divs = document.getElementsByClassName("fbus_arxrow");
    for(var j = divs.length - 1; j >= 0; j--) {
	freeFlash(null,divs[j],divs[j].nextSibling);
	//log("got user data src: "+divs[j].dataset.src);
	// divs[j].nextSibling.setAttribute("src", divs[j].dataset.src);
	// divs[j].nextSibling.setAttribute("data", divs[j].dataset.data);
	// divs[j].parentNode.removeChild(divs[j]);
    }
};

function getElementsByTagNames(list, obj) { //find within obj (optional)
    if ( ! obj || ! obj.getElementsByTagName) var obj = window.document;
    var tags = list.split(","); 
    var resultArray = [];
    if (obj.tagName && list.match(obj.tagName.toLowerCase()))
	resultArray.push(obj);
    for ( var i=0; i < tags.length; i++ ) {
	var matched_elements = obj.getElementsByTagName(tags[i].trim());
	for (var j=0, e; e=matched_elements[j], j < matched_elements.length; j++) {
	    resultArray.push( matched_elements[j] );
	    //log("Push "+e.tagName+", id: "+e.id);
	}
    }
    return resultArray;
}

function getById(id) {
    var elem=window.document.getElementById(id);
    if ( ! elem) { elem=new Boolean(); elem.style={}; }
    return elem;
}

if (!Chrome) registerMenus();

function registerMenus() {
    submenuModule.register("Simple Video Audio Blocker");
    GM_registerMenuCommand( "========Simple Video Blocker======", function(){});

    GM_registerMenuCommand("Toggle Simple Video Blocker for this site, Alt-c", toggleBlocking);


    GM_registerMenuCommand("Simple Video Blocker, block frames too, adverts ["+(block_frames ? "on" : "off")+"]", function(){
    block_frames^=true;
	GM_setValue("block_frames", block_frames);
	if (block_frames) initial_css="frame, ifame,"+initial_css;
	alert("Changed frame blocking, reload page or open new tabs for effect.  "
	      +(block_frames?"Even frames on unblocked sites will be blocked.  ":"Frames no longer blocked.") );
    });
    
    GM_registerMenuCommand( "_____________________________________", function(){});
}

function iframe() {              try { 
    return window.parent != window; } catch(e){    
	throw(null); }//GM2_log(e+", error with window parent at: "+location); }
		  }

function srcUrl(el, tscript) { 
    if ( ! tscript) {
	if ( youtube ) {
	    var loc=location+"";
	    return "http://keepvid.com/?url="+loc+"#info";
	    //"http://keep" + loc.substr(  loc.indexOf("youtube") );
	}
    }
    else {
	if ( youtube) {
	    var loc=location+"";
	    var v=loc.substr(  loc.indexOf("v=") );
	    return "http://video.google.com/timedtext?lang=en&"+v;
	}
    }

    function srcOK(src) {	if (src && ! /player/.test(src)) return true;      };
    
    getElementByName(el, "src");
    if (srcOK(el.src)) return el.src; 
    if (srcOK(el.data)) return el.data;
    src=$(el).find("source").attr("src");
    if (src) return src;
    var src=getElementByName(el, "src");
    if (srcOK(src)) return src.value;
    var fvar=getElementByName(el, "flashvars");
    if ( fvar ) {
	var v=decodeURIComponent(fvar.value);
	//log("flashVars for OBJECT: "+v);
	var url_pos=v.indexOf("http:");
	var end_pos=v.indexOf('"', url_pos);//v.indexOf("&quot;", url_pos);
	if (end_pos==-1) end_pos=Infinity; //v.indexOf("&", url_pos);
	v=v.substring(url_pos, end_pos);
	//log("flash URL is: "+decodeURIComponent(v));
	return decodeURIComponent(v);
    }
    return "" ;
}

function tryDownloader() {
    vids=document.getElementsByTagName("video");
    //log("try dload "+vids.length);
    if ( ! vids.length) return;
    var id=document.getElementsByTagName("a");
    var idstr=id[0] ? id[0].getAttribute("id") : null;
    var metas=document.getElementsByTagName("meta");
    var base=metas[0].getAttribute("base");
    var v=vids[0];
    var d=document.createElement("div");
    var src=v.getAttribute("src");
    src=src.substr(src.indexOf(":")+1);
    src=base+"/"+src;
    d.textContent = src;
    var l=document.createElement("a");
    l.href=src;
    l.setAttribute("id","fbsmil");
    //l.textContent=" Is the LInk "+idstr;
    d.innerHTML += "<p><br>The video</br></p>";
    v.parentNode.appendChild(d);
    v.parentNode.appendChild(l);
    if ( ! idstr ) {
	var styles=document.getElementsByTagName("style"), roll="";
	//log("styles len : "+styles.length);
	clear_styles=true;
	while( styles.length )  {
	    var s=styles[0];
	    var p=s.parentNode;
	    //log("rem STLY "+s.textContent);
	    p.removeChild(s);
	    roll+=s.textContent;//="";
	    //log("Sty parent:  "+s.parentNode);
	}
	window.open(src, "_blank");
    }
}

String.prototype.lastword = function (sep) {
    var ar=this.split(sep||" ");
    var lw=ar.pop();
    while (lw=="") lw=ar.pop();
    return lw;
};

//
// MutationObserver functions.           Eg, var obs=nodeInsertedListener(document,"#results", myCBfunc);  function myCBfunc(foundArrayOfNodes, ancestorOfMutation);
//
var nmcnt=1;
function nodeMutation(target, selector, callback, type, include_subnodes) { //type new ones, 1, removed, 2 or both, 3.
    var new_node_obs=new MutationObserver(mutantNodesObserver);
    //log("Setup nodeMutation, selector="+selector+", type="+type+", on target "+nodeInfo(target)+ " nsels:" + document.querySelectorAll(selector).length);
    new_node_obs.observe(target, { subtree: true, childList: true } );
    return new_node_obs;

    function mutantNodesObserver(mutations, mu_obs) {
	var sel_find, muts, node, ditch;
	//log("mutations on "+selector+" "+mutations.length+" cnt:"+nmcnt)
	//console.time("a");
	for(var i=0; i<mutations.length && !ditch; i++) {
	    if (type!=2) testNodes(mutations[i].addedNodes, mutations[i].target);
	    if (type!=1) testNodes(mutations[i].removedNodes);
	}
	//console.timeEnd("a"); // 32msec for 3900 mutations.
	//log(mutations.length+" mutations END. ");
	
	function testNodes(nodes, ancestor) {
	    if (ancestor && ! ancestor.querySelector(selector)) return; //assuming most do not match.   //if (!f.data("nmutfired")) {  //jq data, unlike DOM dataset, is removed with node removal (unless detach is used), it is stored on all elements that match at the time and not on new ones.
	    var results=[], exact=false;
	    for (var j=0,node; node=nodes[j], j<nodes.length && !ditch;j++) {
		//log(results.length,"NewNode "+nodeInfo(node,true));
		if (node.nodeType!=1) continue;
		if (include_subnodes) //.innerHTML can add subnodes that do not get included in mutations, these lower nodes are checked here.
		    results=results.concat(Array.prototype.slice.call(node.querySelectorAll(selector)));
		if (node.matches(selector)) 
		    results.push(node);
	    }
	    if (results.length) ditch=callback(results, ancestor);
	} //testNodes()
    };//mutantNodesObserver()
} //nodeMutation()

function nodeInsertedListener(target, selector, callback, include_subnodes) {
    return nodeMutation(target,selector,callback,1, include_subnodes);
}
function nodeRemovedListener(target, selector, callback, include_subnodes) {
    return nodeMutation(target,selector,callback,2, include_subnodes);
}
function nodeMutationListener(target, selector, callback, include_subnodes) {
    return nodeMutation(target,selector,callback,3, include_subnodes);
}

function attrModifiedListener(target, attr, callback) { //attr is array or not set.
    var attr_obs=new MutationObserver(attrObserver);
    var config={ subtree:true, attributes:true, attributeOldValue:true};
    if (attr) config.attributeFilter=attr;
    attr_obs.observe(target, config);
    function attrObserver(mutations) {
	for(var i=0, mut;mut=mutations[i], i<mutations.length; i++) {
	    if ( ! ( mut.attributeName=="id" && !mut.id ) ) //infinite loop w/o check for null, sizzle or such changes attrib to null as side effect.
		callback(mutations[i]);
	}
    }
    return attr_obs;
}
//
// End MutationObserver functions.  Eg, var obs=nodeInsertedListener(document,"div.results", myCBfunc);  function myCBfunc(foundArrayOfNodes, ancestorOfMutation);
//

function nodeInfo(node1,plevel,...nodes) { // show DOM node info or if name/value object list name=value
    plevel=plevel||1;
    if (isNaN(plevel) && plevel) { nodes.unshift(node1,plevel); plevel=1; }
    else nodes.unshift(node1);
    plevel--;
    return nodes.map(node=> {
	if (!node || typeof node=="string") return node;
	if (node && node.attr) node=node[0];
	if (node && node.appendChild) {
	    let classn=node.className ? node.className.replace("Web-Eraser-ed","") : "";
	    return node ? node.tagName.toLowerCase() + classn.replace(/^\b|\s+(?=\w+)/gi, ".").trim() + (node.id||"").replace(/^\s*\b\s*/,"#")
	    + (plevel>0 ? "<" + nodeInfo(node.parentNode,plevel):"")
	    : "<empty>";
	}
	else if (node.cssText) return node.cssText;
	else
	    return ""+Object.entries(node)      // entries => array of 2 member arrays [[member name,value]...]
	    .filter(x=> isNaN(x[0]) && x[1] )  //Only name value members of object converted to string.
	    .map(x=>x[0]+":"+x[1]).join(", ");
    }).join(" ");
}

function kinship(){return "";}

function  jqget(url,fname) {
    //var url="http://cougar-tower/root/downloads/IMG_20150528_173009.jpg"
    //url=    "http://cougar-tower/root/downloads/kraftwerk.radioactivity.mp4"
    //url="http://cdn2b.video.pornhub.phncdn.com/videos/201107/19/4032302/vl_480P_262.0k_4032302.mp4?ipa=109.255.76.176&rs=41&ri=1000&s=1473431444&e=1473438644&h=adfcedb74327a54e5b1055c9bab52984"
    $("#vidasimg").addClass("getBinary"+tstamp());
    getBinaryData(url,fname||"blob.data",function(bdata) {
	$("#vidasimg").addClass("sendBinary"+tstamp());
	console.log("getBinaryData called CB.");
	//!!sendBinaryData("http://cougar-tower/php.index.php",bdata);
    });
    
}//jqget()


function getBinaryData(url,fname,cb) {
    console.log("getBinaryData make call",url, fname);
    var xhr = new XMLHttpRequest(), bcount=0;
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';
    //xhr.setRequestHeader('Range', 'bytes=0-');//10k // 10240 ==> 10420224-10821122/10821123.  No supported in server?
    xhr.onload = function(e) {
	if (this.status == 200) {
	    var result=this.response;
	    var blob = new Blob([result],{type:'application/octet-binary'});
	    var fd = new FormData();
	    fd.append('fname', fname);
	    fd.append('data', blob);
	    var responheaders=this.getAllResponseHeaders();
	    console.log("getBinaryData(), OK got url:",url,", typeof res:",typeof result, "blob.size:",blob.size,", K:", blob.size/1024|0,", bcount:",bcount,responheaders);
	    cb(fd);
	}
	else {
	    $("#vidasimg").addClass("getBinaryError"+tstamp()+"-"+this.status);
	    console.error("xhr bad starus,",this.status,xhr.statusText,url);
	}
    };
    xhr.onerror = function (e) {
	console.error("getBinaryData, onerror, xhr:",xhr,", e:",e,bcount,xhr.getAllResponseHeaders());
	$("#vidasimg").addClass("getBinaryError"+tstamp());
	alert("xhr onerror "+e);
    };
    xhr.ontimeout = function (e) {
        console.error("The request for " + url + " timed out.",e);
    };
    // xhr.onreadystatechange = function () {
    //         console.log("onreadystatechange",this);
    // };
    xhr.onabort = function (e) {
        console.log("onabort",xhr,e);
    };
    xhr.onprogress = function (e) {
	bcount++;
	if (bcount%591==0) // factor seen as 17732.98411829134720700985, so 59.131 times is MB
            console.log("onprogress 591 onprogress calls, ~1MB.",e,this);
    };
    xhr.onloadend=function (e) {
        console.log("onloadend",xhr,e);
    };
    xhr.send();
} //getBinaryData

function tstamp() {
    var d=new Date();
    return "-"+d.getHours()+"-"+d.getMinutes();
}

function sendBinaryData(url,bdata){
    console.log("sendBinaryData() begin ",url, "data.length:",bdata.get("data").size);
    var jqxhr=$.ajax({
	type: "POST",
	url: url,
	data: bdata,
	contentType: 'application/octet-stream', //changes var in php not in _POST
	headers:      {fname:bdata.get("fname")},
	processData: false 
    }).done(function(replydata,status) {
	console.log("POST of data done",status,"replydata.length:",replydata.length,replydata);
	$("#vidasimg").addClass("sendBinaryDone"+tstamp());
	//document.write(replydata);
	console.log("location is:",location);
    }).fail(function(e,f) {
	console.log("sendBinaryData fail",e,f,bdata);
	$("#vidasimg").addClass("sendBinaryFail"+tstamp());
	alert( "sendBinaryFail e:"+e );
	console.log("sendBinaryFail status:",jqxhr.statusText); //Use of jqhr here not valid object already detroyed?
    });
    
} //sendBinaryData()

function catchSniffAjaxEvents() {
    var open = window.XMLHttpRequest.prototype.open,  
	send = window.XMLHttpRequest.prototype.send;
    
    function openReplacement(method, url, async, user, password) {  
	this._url = url;
	return open.apply(this, arguments);
    }
    
    function sendReplacement(data) {  
	if(this.onreadystatechange) {
	    this._onreadystatechange = this.onreadystatechange;
	}
	
	console.log('Catcher, Request sent',this,this.response?"Size:"+this.response.size:"","headers:",this.getAllResponseHeaders());
	
	this.onreadystatechange = onReadyStateChangeReplacement;
	return send.apply(this, arguments);
    }
    
    function onReadyStateChangeReplacement() {  
	if(onReadyStateChangeReplacement.state==this.readyState) return;
	onReadyStateChangeReplacement.state=this.readyState;
	//console.log('Ready state changed to: ', this.readyState,this.response?this.response.size:""); //too many
	//console.log("Repsponse headers:",this.getAllResponseHeaders());
	if(this._onreadystatechange) {
	    return this._onreadystatechange.apply(this, arguments);
	}
    }
    
    window.XMLHttpRequest.prototype.open = openReplacement;  
    window.XMLHttpRequest.prototype.send = sendReplacement;
    
}

function getVidLength(url) {
    console.log("getVidLength",url);
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    //xhr.setRequestHeader('Range', 'bytes=1024-');//1k
    //xhr.setRequestHeader('Range', 'bytes=100-200');
    xhr.responseType = 'blob';
    setTimeout(function(){	var range=xhr.getResponseHeader("Content-Range");console.log("aborted ",range,xhr.getAllResponseHeaders());xhr.abort();},5000);
    xhr.onload = function(e) {
	var responheaders=this.getAllResponseHeaders();
	var range=xhr.getResponseHeader("Content-Range");
	
	if (this.status == 200) {
	    var result=this.response;
	    console.log("getVidLength() ",url,responheaders,range);
	}
	else {
	    console.error("xhr bad starus,",this.status,xhr.statusText,url,responheaders);
	}
    };
    xhr.onerror = function (e) {
	var responheaders=this.getAllResponseHeaders();
	var range=xhr.getResponseHeader("Content-Range");
	console.error("getVidLength onerror, xhr:",xhr,", e:",e,"responheaders:",responheaders,"range:",range);
    };
    xhr.onabort = function (e) {
	var responheaders=this.getAllResponseHeaders();
	var range=xhr.getResponseHeader("Content-Range");
	console.error("getVidLength onerror, xhr:",xhr,", e:",e,"responheaders:",responheaders,"range:",range);
    };

    xhr.send();
}


/////////////////
/////////////////// ////////////WRAPPER for Google Chrome etc.///////////////////////////////////////////
///////////////////Wrapper version 4.0.3
// Notes: the this pointer on chrome may differ from ff.
//              keypress does not pass on altKey setting (charCode is not set for keypress but for keydown for both).
//
// Functions: 1. Provides platform independence, certain functions on Firefox but missing on Chrome are provided.
//            2. Alert and prompt windows of reasonable size compared to restricted native ones.
//            3. GMsetValue etc. overridden to write/read json values.
//            4. On Chrome GM menu is provided as an GM icon top right, it overrides GM_registerMenuCommand().  Now using GM_registerMenuCommand JS module.
//            5. On Chrome enables dynamic loading of jquery and jqueryui.
//            6. On Chrome provides access to own version of status bar.

function GM_platform_wrapper(title, use_jquery, loadedCB) {  //use_jquery =[1,2,4,6], bit 2 for UI, eg for prompt3 function, bit 4 for new GM_menu registration.
    var csname=title.replace(/\W*/g,""), uwin=unsafeWindow, bg_color="rgb(173,216,239, 0.8)",depends=[],tmp_store={}; //"#add8e6"
    String.prototype.parse = function (r, limit_str) { var i=this.lastIndexOf(r);var end=this.lastIndexOf(limit_str);if (end==-1) end=this.length; if(i!=-1) return this.substring(i+r.length, end); };  //return string after "r" and before "limit_str" or end of string. 
    String.prototype.trim = function (charset) { if (!charset) return this.replace(/^\s*|\s*$/g,""); else return this.replace( RegExp("^["+charset+"]*|["+charset+"]*$", "g" ) , "");}; //trim spaces or any set of characters.
    window.outerHTML = function (obj) { return new XMLSerializer().serializeToString(obj); };
    window.FireFox=false;     window.Chrome=false; window.uscript_name=title;
    window.confirm3=confirm3;  window.prompt3=prompt3;  window.alert3=alert3; 
    window.stringify=JSON.stringify;
    window.local_getValue=local_getValue; window.local_setValue=local_setValue; 
    
    //problem with localStorage is that webpage has full access to it and may delete it all, as bitlee dotcom does at very end, after beforeunload & unload events.
    function local_setValue(name, value) { name="GMxs_"+name; if ( ! value && value !=0 ) {   try{ localStorage.removeItem(name); } catch(e){};      return;    }
					   var str=JSON.stringify(value);  try {localStorage.setItem(name,  str );}catch(e){}
					 }
    function local_getValue(name, defaultValue) { name="GMxs_"+name;  var value = null; try{value = localStorage.getItem(name);}catch(e){};    if (value==null) return defaultValue;    
						 value=JSON.parse(value);    return value;  
					       }   //on FF it's in webappsstore.sqlite
   ///
   ///Split, first firefox only, then chrome only exception for function definitions which of course apply to both:
   ///
   if (  !  /^Goo/.test (navigator.vendor) )  { /////////Firefox:
       window.FireFox=true;
       window.brversion=parseInt(navigator.userAgent.parse("Firefox/"));
       var old_set=GM_setValue, old_get=GM_getValue;
       GM_setValue=function(name, value) { return old_set( name, JSON.stringify(value));	};
       GM_getValue=function(name, defaulT) { var res=old_get ( name, JSON.stringify (defaulT) );  if (res!="") try { return JSON.parse(res); } catch(e) { console.info(uscript_name+" during parse, JSON Error: "+name+", will return result anyway, type:"+(typeof res)+", value:"+res+", error:"+e);   }	 return res; };	//rmed eval less backcompat
       if (use_jquery&2 && ! document.getElementById("jqueryuiCss") ) {
	   var src=GM_getResourceText("jqueryuiCss");
	   $('head').append("<style id=jqueryuiCss>"+src+"</style>");
	   // $("head").append ("<link id=jqueryuiCss href="+url+"rel=stylesheet type=text/css>" ); //would also load ui images but extra load.
       }
       return;
   } //end ua==Firefox
   /////////////
   ///////////////////// Only Google Chrome from here, except for functions used above defined below :
   ///////////
    window.Chrome=true;
    window.brversion=parseInt(navigator.userAgent.parse("Chrome/"));
    GM_setValue = function(name, value) { name=title+":"+name; local_setValue(name, value);};
    GM_getValue = function(name, defval) { name=title+":"+name; return local_getValue(name, defval); };
    GM_deleteValue = function(name) { localStorage.removeItem(title+":"+name);  };
    
    // Only Chromium not able for @require...
    if (use_jquery) { //Can be 1/true (default jquery), 2: jquery-ui, 4:GM_registerMenuCommand, or 6 both.
	var cb_countdown=1;
	switch(use_jquery) { case 2: cb_countdown+=2;break; case 4: cb_countdown+=1;break; case 6: cb_countdown+=3; }
	if(!window.jQuery)
	    loadScript("https://code.jquery.com/jquery-latest.js");
	else if (use_jquery==1) sourceIn("");
	if (use_jquery & 2) { //010 // eg, parseInt("010", 2) ==> 2
	    loadScript("https://code.jquery.com/ui/1.10.3/jquery-ui.js");
	    loadScript("https://code.jquery.com/ui/1.10.3/themes/vader/jquery-ui.css");
	}
	if (use_jquery & 4) { //100, eg parseInt("100", 2) => 4, 110 => 6
	    loadScript("https://openuserjs.org/src/libs/slow!/GM_registerMenuCommand_Submenu_JS_Module.js");
	}
    }//if use_jquery
    function sourceIn(filename, filetext) {
	if (/\.css$/.test(filename)) GM_addStyle(filetext);
	else tmp_store[filename]=filetext;
	cb_countdown--;
	if (cb_countdown==0) {
	    while(depends.length)
		try { window.unsafeWindow=unsafeWindow;eval.call(window,tmp_store[depends.shift()]); } catch(e){ console.log("Platform Wrapper userscript, js eval err"+e); }
	    setTimeout(loadedCB,0);
	}
    }
    function loadScript(url) {
	var filen=url.split("/").splice(-1);
	var file=GM_getValue(filen,"");
	depends.push(filen);
	if (file) {
	    //console.info("Using cached "+filen+".  To delete, close chromium and find sqlite file in chromium main dir under dir 'Local Storage' as a file with site name suffixed with .locastorage");
	    sourceIn(filen,file); return;
	}
	GM_xmlhttpRequest(  { method: "GET", url: url, onload:function(r) { //asynch
	    GM_setValue(filen, r.responseText);
	    sourceIn(filen, r.responseText);
	} });
    }
    uneval=function(x) {
      return "("+JSON.stringify(x)+")";
    };
    GM_addStyle = function(css, doc) {
	if (!doc) doc=window.document;
	var style = doc.createElement('style');
	style.textContent = css;
	doc.getElementsByTagName('head')[0].appendChild(style);
    };
    function setStatus(s) {
	//if (s)  s = s.toLowerCase ? s.toLowerCase() : s;
	setStatus.value=s;
	var div=document.getElementById("GMstatus");
	if (div) {	
   	    if (s) { div.textContent=s;	    div.style.display="block";	    setDivStyle(); }
   	    else   { setDivStyle();	    div.style.display="none"; }
	} 
	else  if (s) { 
   	    div=document.createElement('div');
   	    div.textContent=s;
   	    div.setAttribute('id','GMstatus');
   	    if (document.body) document.body.appendChild(div);
   	    setDivStyle();
   	    div.addEventListener('mouseout', function(e){ setStatus(); },false);
	}
	if (s) setTimeout( function() {  if (s==setStatus.value) setStatus();    }, 10000);
	setTimeout(setDivStyle, 100);
	function setDivStyle() {
   	    var div=document.getElementById("GMstatus");
   	    if ( ! div ) return;
   	    var display=div.style.display; 
   	    div.style.cssText="border-top-right-radius: 6px; "
		+"background: linear-gradient(#fff,#ddd);"
		+"color: rgba(0,0,0,0.8) ! important; font-weight:500;"  //text-shadow: 0 1px 0 rgba(0,0,0,.4);"
   		+"font-family: Helvetica; font-size: 15px;line-height:20px; z-index: 9909099; "
		+"padding: 2px; padding-top:0px; border: 1px solid #82a2ad; "//Lucida Sans Unicode;
   		+"position: fixed ! important; bottom: 0px; " + (FireFox && brversion >= 4 ? "left: -1px" : "" );
   	    div.style.display=display;
	}
    }//setStatus()
    initStatus();
    function initStatus() {
   	window.__defineSetter__("status", function(val){  setStatus(val); });
   	window.__defineGetter__("status", function(){    return setStatus.value; });
    }
    function prompt3(text, init_value, handler, following_text, title){
	if (!init_value) init_value="";
	var box=confirm3(text, handler, "", "", title, init_value, following_text);
	return box;
    } //prompt3()
    function alert3(text){
	confirm3(text, "", null);
    }
    
    function confirm3(text, handler, btn1, btn2, title, init_answer, following_text ) {
	if(btn1 !== null) btn1=btn1||"Cancel";
	if (!confirm3.cnt) confirm3.cnt=1; else confirm3.cnt++;
	if (!handler) handler=function(){};
	btn2=btn2||"OK";
	title=title||"";
	var topleft_btn="\u2573"; //"x";
	text=text.replace(/</g,"&lt;").replace(/\n/g,"<br>").replace(/\t/g,"&nbsp;&nbsp;&nbsp;&nbsp;");
	following_text=following_text||"";
	following_text=following_text.replace(/</g,"&lt;").replace(/\n/g,"<br>").replace(/\t/g,"&nbsp;&nbsp;&nbsp;&nbsp;");
	var res, buttons={}, box, dinput="";
	if (init_answer!==undefined) {
	    dinput="<input id=fsfdinput style='width:100%; background:white;color:black'></input>";
	    if (!text) dinput="<textarea id=fsfdinput style='width:100%; height:98%; white-space:pre-wrap;'></textarea>";
	}
	box=$("<div id=sfsconfirm3 title=x><div id=sfsmsgtxt></div>"
	      +dinput+"<div id=sfsfollowing></div></div>");
	var ip=box.find("input, textarea");ip.val(init_answer);
	ip.css({bordertyle:"indent", borderWidth:"4px",padding:0,margin:0});
	var msgtxt=box.find("#sfsmsgtxt"); msgtxt.html(text); 
	var followtxt=box.find("#sfsfollowing"); followtxt.html(following_text);
	//GM_addStyle("pre { font-family: Sans-Serif;}"); // if wrap text in <pre>
	$("body").append(box);
	
	buttons[btn2]=function(e) { //OK 
	    var ip=$(this).find("input, textarea");//can't use id since may be duplicated, queueing of dialoges?
	    handler(ip.length?ip[0].value:1);
	    $(this).dialog("close");
	    e.stopPropagation();	    e.preventDefault();
	};
	if(btn1 !== null) buttons[btn1]=function(e) { //cancel
		handler(null); $(this).dialog("close");
		e.stopPropagation();	    e.preventDefault();	 
	};
	box.dialog({ position: "center", modal: true, buttons: buttons, resizable: true,height:"auto", width:"auto", overflow:"auto" }); //wraps box in dialog elems.
	if (!ip[0] || ip[0].tagName=="INPUT")  box.keydown(function (event) {
	    if (event.keyCode == 13)  {   $(this).parent().find("button:eq(1)").trigger("click");    }  });
	//
	// Dialog element has three component elements: titlebar, box and buttonpane.
	var dialog=box.closest(".ui-dialog"), titlebar=$(".ui-dialog-titlebar",dialog), buttonpane=$(".ui-dialog-buttonpane",dialog), limit=window.innerHeight*0.66*0.85, longD;
	if (box.height()>limit) {  longD=true;log("too long "+limit);    box.height(limit);       dialog.css("top", window.innerHeight*(1/7)+"px");   }
	limit=window.innerWidth*.66;
	if (box.width()>limit) {      box.width(limit);    dialog.css("left", window.innerWidth*(1/6)+"px");   }
	dialog.css({ zIndex:2147483642, textAlign:"left", position: "fixed",
		     left:"15%", width:"50%",height:"70%",top:"10%", fontSize:"medium" });
	$(dialog).add(titlebar).css({background:"#002400", boxSizing: "content-box"});
	buttonpane.css({background: "#001800",marginTop:0, boxSizing: "content-box"});
	GM_addStyle(".sfsdiax {white-space:nowrap}");
	$(".ui-button").css("font-size", "small");

	buttonpane.css({width:"-moz-available",position:"absolute",bottom:0});
	box.css({width:"-moz-available",position: "absolute"});
	box.css({background:"#002000", color:"#eeeeee", overflow:"auto", fontSize:"medium" });
	box.children().css({background:"#002000", color:"#eeeeee"});
	dialog.find("*").addClass("sfsdiax");

	with($(".ui-dialog-title"))	{
	    children(":eq(0)").title="close";	width("5%");	css({cursor:"pointer",marginTop:"-4px",marginLeft:"-4px"});
	    click(function() { box.dialog("close"); } );
	    hover(function() { log("ft "); $(this).toggleClass("whitebtn");});
	}
	with(titlebar.find("button")) { text("x"); css({top:7,right:0}); }
	jQuery.fn.reverse = [].reverse;
	if(btn1===null) { var btn=$(".ui-dialog-buttonset .ui-button");btn.css({left:"40%"}); ; 
			  $(".ui-button")[0].focus();$(".ui-dialog-buttonset").css({float:"none"});}
	else $(".ui-dialog-buttonset button").reverse().each(function() {this.focus(); return false;});  //return false, break from each(), return true, equiv to continue
	if (longD) box.focus();
	if (ip[0]) { ip.focus();} //else dialog.focus();
	//console.log("outerHeights:",buttonpane,buttonpane.outerHeight(), titlebar,titlebar.outerHeight());
	titlebar.height("7%");box.height("65%");buttonpane.height("10%");
	box.css({bottom: buttonpane.outerHeight(), top: titlebar.outerHeight(), boxSizing: "content-box" });
	box.scrollTop(0);
	// var wrapper="<div id=iwrapper style='position:fixed !important; width:60%; height:80%;'></div>";
	// dialog.wrap(wrapper);
	// wrapper.draggable();
	return box;
    }//end fun confirm3();
    
} //end platform_wrapper()