Blockhead

Blocks headers and other sticky elements from wasting precious vertical screen estate by pinning them down.

目前为 2017-12-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Blockhead
  3. // @namespace blockhead
  4. // @description Blocks headers and other sticky elements from wasting precious vertical screen estate by pinning them down.
  5. // @match *://*/*
  6. // @version 15
  7. // @grant GM.getValue
  8. // @grant GM.setValue
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_getResourceText
  12. // @grant GM.getResourceText
  13. // @xhr-include *
  14. // @author elypter
  15. // @require https://greasyfork.org/scripts/36900-super-gm-setvalue-and-gm-getvalue-lib/code/Super_GM_setValue_and_GM_getValue%20lib.user.js
  16. // @resource black_keywords https://raw.githubusercontent.com/elypter/rule_keyword_generator/master/generic_rule_keywords.txt
  17. // ==/UserScript==
  18.  
  19.  
  20.  
  21.  
  22. //This script blocks headers and other sticky elements from wasting precious vertical screen estate by pinning them down.
  23. //It checks the computed style of each element. if the position attribute is fixed or absolute or ~relative~
  24. //then it checks if its id or classes names contain provided in the list below.
  25. //this list is created with theis tool https://github.com/elypter/rule_keyword_generator by parsing the rules https://raw.githubusercontent.com/yourduskquibbles/webannoyances/master/ultralist.txt
  26. //for class names and ids.
  27. //There is also a whitelist for certain keywords and tag names to reduce flase positives and processing time.
  28. //currently the processing time is quite high because each element has to be checked and many have to be checked against the whole list.
  29. //on an old laptop it can take up to single digit seconds so pageloadtime noticable increases.
  30. //License: GPL3
  31.  
  32.  
  33. //id and classes that contain any of these keywords will not be modified
  34. var white_names = ["side","guide","article","html5","story","main","left","right","content","account__section","container--wide","container__main","panel",
  35. "body","gutter","embed","watch","background","middleContainer","drag-and-drop"];
  36. //var white_names = ["example-whitelist-entry"];
  37.  
  38. //tags that will not be cheked
  39. var ignore_tags = ["a","A","script","SCRIPT","body","BODY","li","LI","ul","UL","br","BR","h5","H5","b","B","strong","STRONG","svg","SVG","path","PATHH","h2","H2",
  40. "code","CODE","tr","TR","td","TD","h3","H3","h1","H1","h4","H4"];//,"noscript","NOSCRIPT"
  41.  
  42. var count_element_walker=0; //debug:counts how often a page check is triggered
  43. var count_style_walking=0; //debug:counts how often checking stylesheets is triggered
  44.  
  45. //search for floating elements that get added after page load.
  46. var mutation_check=GM_SuperValue.get ("mutation_check")==true?GM_SuperValue.get ("mutation_check"):1; //1=yes 2=no change value in memory tab
  47. GM_SuperValue.set ("mutation_check",mutation_check);
  48.  
  49. //all elements of a site will be checked individually for their computed style and changed if there is a match
  50. var walk_elements=GM_SuperValue.get ("walk_elements")==true?GM_SuperValue.get ("walk_elements"):1; //1=yes 2=no change value in memory tab
  51. GM_SuperValue.set ("walk_elements",walk_elements);
  52.  
  53. //all stylesheets will be checked for classes and changed if there is a match
  54. var walk_styles=GM_SuperValue.get ("walk_styles")==true?GM_SuperValue.get ("walk_styles"):0; //1=yes 2=no change value in memory tab
  55. GM_SuperValue.set ("walk_styles",walk_styles);
  56.  
  57. //this will save the statistics of how often a keyword is being matched in a local variable that can be viewed in the memory tab
  58. var save_keyword_statistics=GM_SuperValue.get ("save_keyword_statistics")==true?GM_SuperValue.get ("save_keyword_statistics"):0; //1=yes 2=no change value in memory tab
  59. GM_SuperValue.set ("save_keyword_statistics",save_keyword_statistics);
  60.  
  61. //this will save the rules generated based on the blocking in a local variable that can be viewed in the memory tab
  62. var save_generated_rules=GM_SuperValue.get ("save_generated_rules")==true?GM_SuperValue.get ("save_generated_rules"):0; //1=yes 2=no change value in memory tab
  63. GM_SuperValue.set ("save_generated_rules",save_generated_rules);
  64.  
  65. //contained in black_keywords.txt
  66. var black_keywords=GM_getResourceText("black_keywords").toString().split("\n"); //list generated with rule_keyword_generator from webannoyances ultralist
  67.  
  68. function counted_element_walker(elm,orig){
  69. count_element_walker++;
  70.  
  71. //this disables all javascript that is being triggered when scrolling
  72. window.addEventListener("scroll", function (event) {
  73. event.stopPropagation();
  74. }, true);
  75. console.log("check number "+count_element_walker+" from: "+orig);
  76. element_walker_all(elm);
  77. //console.log(GM_SuperValue.get ("white_names_counter"));
  78. //console.log(GM_SuperValue.get ("black_keywords_counter"));
  79. //console.log(GM_SuperValue.get ("generated_rules"));
  80. }
  81.  
  82. function keyword_walker(keyword_list){
  83. var white_names_counter=GM_SuperValue.get ("white_names_counter")||{};
  84. var black_keywords_counter=GM_SuperValue.get ("black_keywords_counter")||{};
  85.  
  86. //todo: switch order of for loops to detect whitelists earlier
  87. var state=-1;
  88. //console.log("list: ("+keyword_list.length+") "+keyword_list);
  89. for (var i=0; i < keyword_list.length; i++){
  90. var subword_list=keyword_list[i].toString().split('-');
  91. //console.log("sublen of: "+subword_list+" from "+keyword_list[i]+" = "+subword_list.length);
  92. for (var j=0; j < subword_list.length; j++){
  93. var subsubword_list=subword_list[j].split('_');
  94. //console.log("subsublen of: "+subsubword_list+" from "+subword_list[j]+" = "+subsubword_list.length);
  95. for (var k=0; k < subsubword_list.length; k++){
  96. for (var l=0; l < white_names.length; l++){
  97. //console.log("test white: l:"+white_names[l]+" in s:"+subsubword_list[k]+" of "+keyword_list);
  98. if (subsubword_list[k].indexOf(white_names[l]) != -1){
  99. //console.log("whitelisted: l:"+white_names[l]+" in s:"+subsubword_list[k]+" of "+keyword_list);
  100. if(white_names_counter[white_names[l]]) white_names_counter[white_names[l]]++;
  101. else white_names_counter[white_names[l]]=1;
  102. GM_SuperValue.set ("white_names_counter", white_names_counter);
  103. return 0;
  104. }
  105. }
  106. for (var l=0; l < black_keywords.length; l++){
  107. if (subsubword_list[k].indexOf(black_keywords[l]) != -1){
  108. console.log("matched: l:"+black_keywords[l]+" in s:"+subsubword_list[k]+" of "+keyword_list);
  109. if(black_keywords_counter[black_keywords[l]]) black_keywords_counter[black_keywords[l]]++;
  110. else black_keywords_counter[black_keywords[l]]=1;
  111. state = 1;
  112. }
  113. }
  114. }
  115. }
  116. }
  117.  
  118. GM_SuperValue.set ("black_keywords_counter", black_keywords_counter);
  119. return state;
  120. }
  121.  
  122. function element_walker_all(startElem) {
  123. //walk over element list as opposed to an element tree
  124. if (!(startElem instanceof Element)) return;
  125.  
  126. var generated_rules=GM_SuperValue.get ("generated_rules")||[]; //contains all adblock/ublock rules that the script creates based on what it modifies
  127.  
  128. var elms = startElem.getElementsByTagName("*");
  129. for (var x = elms.length; x--;) {
  130. elm=elms[x];
  131. //console.log("checking: "+elm.tagName.toString());
  132. if(elm instanceof Element && ignore_tags.indexOf(elm.tagName.toString()) == -1 && getComputedStyle(elm)) {
  133. //console.log("testing: "+elm.tagName.toString()+" with position= "+getComputedStyle(elm).getPropertyValue("position").toLowerCase());
  134. if (/*(getComputedStyle(elm).getPropertyValue("position") == "absolute") ||*/
  135. (getComputedStyle(elm).getPropertyValue("position").toLowerCase() == "fixed")/* || */
  136. /*(getComputedStyle(elm).getPropertyValue("position") == "relative")*//* ||*/
  137. /*(getComputedStyle(elm).getPropertyValue("top") != "")*/) {
  138.  
  139. var keyword_list =[];
  140. keyword_list=elm.className.toString().split(' ');
  141. if (typeof(elm.id)=="string"&&elm.id!="") keyword_list.push([elm.id]);
  142. if (typeof(elm.tagName)=="string"&&elm.tagName!="") keyword_list.push([elm.tagName]);
  143.  
  144. var has_matched=-1;
  145. if (keyword_list!=[]&&keyword_list!=[""]){
  146. //compare against black and whitelist
  147. has_matched=keyword_walker(keyword_list);
  148. }
  149. var rule;
  150. var class_list=elm.className.toString().split(' '); //check for rule writing
  151. if (has_matched==1){
  152. console.log("pinning sticky: "+elm.id+","+elm.className+","+elm.tagName);
  153.  
  154. if (elm.id){
  155. rule=window.location.hostname+"###"+elm.id+":style(position: "+"fixed"+" !important;)";
  156. if(generated_rules.indexOf(rule)==-1){
  157. if(!(rule in generated_rules)) generated_rules.push(rule);
  158. //console.log(rule);
  159. }
  160. }
  161. if(elm.className){
  162. for (var i=0; i < class_list.length; i++){
  163. rule=window.location.hostname+"##."+class_list[i]+":style(position: "+"fixed" +" !important;)";
  164. if(generated_rules.indexOf(rule)==-1){
  165. if(!(rule in generated_rules)) generated_rules.push(rule);
  166. //console.log(rule);
  167. }
  168. }
  169. }
  170. elm.setAttribute('style', 'position:static !important');
  171. elm.style.removeProperty('top');
  172. //return;
  173. }else if(has_matched==0){
  174. if (elm.id){
  175. rule=window.location.hostname+"#@#"+elm.id;
  176. if(generated_rules.indexOf(rule)==-1){
  177. if(!(rule in generated_rules)) generated_rules.push(rule);
  178. //console.log(rule);
  179. }
  180. }
  181. if(elm.className){
  182. for (var j=0; j < class_list.length; j++){
  183. rule=window.location.hostname+"#@."+class_list[j];
  184. if(generated_rules.indexOf(rule)==-1){
  185. if(!(rule in generated_rules)) generated_rules.push(rule);
  186. //console.log(rule);
  187. }
  188. }
  189. }
  190. console.log("whitelisted sticky: "+elm.id+","+elm.className+","+elm.tagName);
  191. }else{
  192. console.log("ignoring sticky: "+elm.id+","+elm.className+","+elm.tagName);
  193. }
  194. }
  195. }
  196.  
  197.  
  198.  
  199. }
  200. GM_SuperValue.set ("generated_rules", generated_rules);
  201. }
  202.  
  203. function element_walker(elm) {
  204. //walk over element list as opposed to an element tree
  205. var node;
  206. //console.log("checking: "+elm.tagName.toString());
  207. if(elm instanceof Element && ignore_tags.indexOf(elm.tagName.toString()) == -1 && getComputedStyle(elm)) {
  208. //console.log("testing: "+elm.tagName.toString()+" with position= "+getComputedStyle(elm).getPropertyValue("position").toLowerCase());
  209. if (/*(getComputedStyle(elm).getPropertyValue("position") == "absolute") ||*/
  210. (getComputedStyle(elm).getPropertyValue("position").toLowerCase() == "fixed")/* || */
  211. /*(getComputedStyle(elm).getPropertyValue("position") == "relative")*//* ||*/
  212. /*(getComputedStyle(elm).getPropertyValue("top") != "")*/) {
  213.  
  214. var keyword_list =[];
  215. keyword_list=elm.className.toString().split(' ');
  216. if (typeof(elm.id)=="string"&&elm.id!="") keyword_list.push([elm.id]);
  217.  
  218. var has_matched=-1;
  219. if (keyword_list!=[]&&keyword_list!=[""]){
  220. has_matched=keyword_walker(keyword_list);
  221. }
  222. var rule;
  223. var class_list=elm.className.toString().split(' '); //check for rule writing
  224. if (has_matched==1){
  225. //console.log("pinning sticky: "+elm.id+","+elm.className+","+elm.tagName);
  226.  
  227. if (elm.id){
  228. rule=window.location.hostname+"###"+elm.id+":style(position: "+"fixed"+" !important;)";
  229. if(generated_rules.indexOf(rule)==-1){
  230. generated_rules.push(rule);
  231. console.log(rule);
  232. }
  233. }
  234. if(elm.className){
  235. for (var i=0; i < class_list.length; i++){
  236. rule=window.location.hostname+"##."+class_list[i]+":style(position: "+"fixed" +" !important;)";
  237. if(generated_rules.indexOf(rule)==-1){
  238. generated_rules.push(rule);
  239. console.log(rule);
  240. }
  241. }
  242. }
  243. elm.setAttribute('style', 'position:static !important');
  244. elm.style.removeProperty('top');
  245. //return;
  246. }else if(has_matched==0){
  247. if(elm.className){
  248. for (var j=0; j < class_list.length; j++){
  249. rule="@@"+window.location.hostname+"##."+class_list[j];
  250. if(generated_rules.indexOf(rule)==-1){
  251. generated_rules.push(rule);
  252. console.log(rule);
  253. }
  254. }
  255. }
  256. //return;
  257. }else{
  258. //console.log("ignoring sticky: "+elm.id+","+elm.className+","+elm.tagName);
  259. }
  260. }
  261. }
  262.  
  263. // Handle child elements
  264. for (node = elm.firstChild; node; node = node.nextSibling) {
  265. if (node.nodeType === 1) { // 1 == Element
  266. element_walker(node);
  267. }
  268. }
  269.  
  270.  
  271. }
  272.  
  273. function style_walker() {
  274. //this alternative mode of searching and modifying sticky elements by searching stylesheets directly
  275. //although i thought stylesheet checking would be faster it tends be slower and cannot be performed on external stylesheets
  276.  
  277. var state=0;
  278. count_style_walking++;
  279. //console.log("checking stylesheets for the "+count_style_walking+"th time");
  280. var styleSheets = window.document.styleSheets;
  281.  
  282. for(var i = 0; i < styleSheets.length; i++){
  283. //console.log("checking stylesheet #"+i);//+" named "+styleSheets[i].sheet);
  284.  
  285. //try to get a list of classes for each stylesheet.
  286. //this will throw an error if the stylesheet is hosted on an external server because of cross site protection
  287. //these stylesheets cannot be processed at them moment/or never
  288. var classes;
  289. if (document.styleSheets[i].cssRules){
  290. classes=document.styleSheets[i].cssRules;
  291. }else if (document.styleSheets[i].rules){
  292. classes=document.styleSheets[i].rules;
  293. }//}catch(e){}
  294. if (!classes) continue;
  295.  
  296. for (var j = 0; j < classes.length; j++) {
  297. //console.log("checking class "+classes[x].selectorText);
  298. state=0;
  299. for (var k=0; k < black_keywords.length; k++){
  300. if (classes[j].selectorText) if (classes[j].selectorText.indexOf(black_keywords[k])!=-1){
  301. //console.log("matched: l:"+black_keywords[k]+" in s:"+classes[j].selectorText+" of "+styleSheets[i].sheet);
  302. if (classes[j].position=="absolute" ||classes[j].position=="static"){
  303. state = 1;
  304. }
  305. }
  306. }
  307. for (var k=0; k < white_names.length; k++){
  308. if (classes[j].selectorText) if (classes[j].selectorText.indexOf(white_names[k])!=-1){
  309. //console.log("whitelisted: l:"+white_names[k]+" in s:"+classes[j].selectorText+" of "+styleSheets[i].sheet);
  310. state=0;
  311. }
  312. }
  313. if (state==1){
  314. stylesheet.deleteRule("position");
  315. }
  316. }
  317. }
  318. }
  319.  
  320. // show saved rules when opening a location containing "bloackhead-rules"
  321. if(window.location.toString().indexOf("blockhead-rules")!=-1){
  322. var iDiv = document.createElement('div');
  323. iDiv.id = 'blockhead-rules';
  324. iDiv.style.margin = '0 auto';
  325. iDiv.style.position= "absolute";
  326. iDiv.style.backgroundColor ="white";
  327. iDiv.style.zIndex=9001000;
  328. iDiv.style.opacity=1;
  329. document.getElementsByTagName('body')[0].appendChild(iDiv);
  330.  
  331. rules=GM_SuperValue.get ("generated_rules");
  332. for(var i=0;i<rules.length;i++){
  333. iDiv.innerHTML+=rules[i]+'<br/>\n';
  334. }
  335. throw new Error('This is not an error. This is just to abort javascript');
  336. }
  337. // show saved rules when opening a location containing "bloackhead-statistics"
  338. if(window.location.toString().indexOf("blockhead-statistics")!=-1){
  339. var iDiv = document.createElement('div');
  340. iDiv.id = 'blockhead-statistics';
  341. iDiv.style.margin = '0 auto';
  342. iDiv.style.position= "absolute";
  343. iDiv.style.backgroundColor ="white";
  344. iDiv.style.zIndex=9001000;
  345. iDiv.style.opacity=1;
  346. document.getElementsByTagName('body')[0].appendChild(iDiv);
  347.  
  348. iDiv.innerHTML='#white_names_counter:<br/>\ņ';
  349. rules=GM_SuperValue.get ("white_names_counter");
  350. for (var key in rules) {
  351. iDiv.innerHTML+=key+": "+rules[key]+'<br/>\n';
  352. }
  353. iDiv.innerHTML+='<br/>\n############<br/>\n<br/>\n';
  354. iDiv.innerHTML+='#black_keyword_counter:<br/>\ņ';
  355. rules=GM_SuperValue.get ("black_keywords_counter");
  356. for (var key in rules) {
  357. iDiv.innerHTML+=key+": "+rules[key]+'<br/>\n';
  358. }
  359. throw new Error('This is not an error. This is just to abort javascript');
  360. }
  361.  
  362. // Kick it off starting with the `body` element
  363. if(walk_styles==1) style_walker();
  364. if(walk_elements==1) counted_element_walker(document.body,"onstart");
  365.  
  366. // check elements again if the page code changes
  367. if(walk_elements==1) document.onload = counted_element_walker(document.body,"onload");
  368.  
  369. // check elements again if the page code changes
  370. if(walk_elements==1) document.onchange = counted_element_walker(document.body,"onchange");
  371.  
  372. // check elements again if the user scrolls the page(doesnt really do anything)
  373. if(walk_elements==1) document.onscroll = counted_element_walker(document.body,"onscroll");
  374.  
  375.  
  376.  
  377. //check if the dom is modified and checks the changed elements again
  378. if(mutation_check==1 && walk_elements==1){
  379. MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  380.  
  381. // define a new observer
  382. var obs = new MutationObserver(function(mutations, observer) {
  383. // look through all mutations that just occured
  384. for(var i=0; i<mutations.length; ++i) {
  385. for(var j=0; j<mutations[i].addedNodes.length; ++j) {
  386. //check this mutation
  387. counted_element_walker(mutations[i].addedNodes[j],"onmutation");
  388. }
  389. }
  390. });
  391.  
  392. // have the observer observe for changes in children
  393. obs.observe(document.body, {
  394. attributes : true, //check for attribute changes
  395. childList: true, //use a list of occured changes
  396. subtree: false //also return all subelements of a change
  397. });
  398. }