Advertisement
Guest User

contentscript-end.js

a guest
Dec 1st, 2015
143
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*******************************************************************************
  2.  
  3.     µBlock - a browser extension to block requests.
  4.     Copyright (C) 2014 Raymond Hill
  5.  
  6.     This program is free software: you can redistribute it and/or modify
  7.     it under the terms of the GNU General Public License as published by
  8.     the Free Software Foundation, either version 3 of the License, or
  9.     (at your option) any later version.
  10.  
  11.     This program is distributed in the hope that it will be useful,
  12.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.     GNU General Public License for more details.
  15.  
  16.     You should have received a copy of the GNU General Public License
  17.     along with this program.  If not, see {http://www.gnu.org/licenses/}.
  18.  
  19.     Home: https://github.com/gorhill/uBlock
  20. */
  21.  
  22. /* global vAPI, HTMLDocument, XMLDocument */
  23.  
  24. /******************************************************************************/
  25.  
  26. // Injected into content pages
  27.  
  28. (function() {
  29.  
  30. 'use strict';
  31.  
  32. /******************************************************************************/
  33.  
  34. // https://github.com/chrisaljoudi/uBlock/issues/464
  35. if ( document instanceof HTMLDocument === false ) {
  36.     // https://github.com/chrisaljoudi/uBlock/issues/1528
  37.     // A XMLDocument can be a valid HTML document.
  38.     if (
  39.         document instanceof XMLDocument === false ||
  40.         document.createElement('div') instanceof HTMLDivElement === false
  41.     ) {
  42.         return;
  43.     }
  44. }
  45.  
  46. // I've seen this happens on Firefox
  47. if ( window.location === null ) {
  48.     return;
  49. }
  50.  
  51. // This can happen
  52. if ( typeof vAPI !== 'object' ) {
  53.     //console.debug('contentscript-end.js > vAPI not found');
  54.     return;
  55. }
  56.  
  57. // https://github.com/chrisaljoudi/uBlock/issues/587
  58. // Pointless to execute without the start script having done its job.
  59. if ( !vAPI.contentscriptStartInjected ) {
  60.     return;
  61. }
  62.  
  63. // https://github.com/chrisaljoudi/uBlock/issues/456
  64. // Already injected?
  65. if ( vAPI.contentscriptEndInjected ) {
  66.     //console.debug('contentscript-end.js > content script already injected');
  67.     return;
  68. }
  69. vAPI.contentscriptEndInjected = true;
  70. vAPI.styles = vAPI.styles || [];
  71.  
  72. /******************************************************************************/
  73.  
  74. var messager = vAPI.messaging.channel('contentscript-end.js');
  75.  
  76. // https://github.com/gorhill/uMatrix/issues/144
  77. vAPI.shutdown.add(function() {
  78.     messager.close();
  79. });
  80.  
  81. /******************************************************************************/
  82. /******************************************************************************/
  83.  
  84. // https://github.com/chrisaljoudi/uBlock/issues/7
  85.  
  86. var uBlockCollapser = (function() {
  87.     var timer = null;
  88.     var requestId = 1;
  89.     var newRequests = [];
  90.     var pendingRequests = {};
  91.     var pendingRequestCount = 0;
  92.     var src1stProps = {
  93.         'embed': 'src',
  94.         'iframe': 'src',
  95.         'img': 'src',
  96.         'object': 'data'
  97.     };
  98.     var src2ndProps = {
  99.         'img': 'srcset'
  100.     };
  101.  
  102.     var PendingRequest = function(target, tagName, attr) {
  103.         this.id = requestId++;
  104.         this.target = target;
  105.         this.tagName = tagName;
  106.         this.attr = attr;
  107.         pendingRequests[this.id] = this;
  108.         pendingRequestCount += 1;
  109.     };
  110.  
  111.     // Because a while ago I have observed constructors are faster than
  112.     // literal object instanciations.
  113.     var BouncingRequest = function(id, tagName, url) {
  114.         this.id = id;
  115.         this.tagName = tagName;
  116.         this.url = url;
  117.         this.collapse = false;
  118.     };
  119.  
  120.     var onProcessed = function(response) {
  121.         // https://github.com/gorhill/uMatrix/issues/144
  122.         if ( response.shutdown ) {
  123.             vAPI.shutdown.exec();
  124.             return;
  125.         }
  126.  
  127.         var requests = response.result;
  128.         if ( requests === null || Array.isArray(requests) === false ) {
  129.             return;
  130.         }
  131.         var selectors = [];
  132.         var i = requests.length;
  133.         var request, entry, target, value;
  134.         while ( i-- ) {
  135.             request = requests[i];
  136.             if ( pendingRequests.hasOwnProperty(request.id) === false ) {
  137.                 continue;
  138.             }
  139.             entry = pendingRequests[request.id];
  140.             delete pendingRequests[request.id];
  141.             pendingRequestCount -= 1;
  142.  
  143.             // https://github.com/chrisaljoudi/uBlock/issues/869
  144.             if ( !request.collapse ) {
  145.                 continue;
  146.             }
  147.  
  148.             target = entry.target;
  149.  
  150.             // https://github.com/chrisaljoudi/uBlock/issues/399
  151.             // Never remove elements from the DOM, just hide them
  152.             target.style.setProperty('display', 'none', 'important');
  153.  
  154.             // https://github.com/chrisaljoudi/uBlock/issues/1048
  155.             // Use attribute to construct CSS rule
  156.             if ( (value = target.getAttribute(entry.attr)) ) {
  157.                 selectors.push(entry.tagName + '[' + entry.attr + '="' + value + '"]');
  158.             }
  159.         }
  160.         if ( selectors.length !== 0 ) {
  161.             messager.send({
  162.                 what: 'cosmeticFiltersInjected',
  163.                 type: 'net',
  164.                 hostname: window.location.hostname,
  165.                 selectors: selectors
  166.             });
  167.         }
  168.         // Renew map: I believe that even if all properties are deleted, an
  169.         // object will still use more memory than a brand new one.
  170.         if ( pendingRequestCount === 0 ) {
  171.             pendingRequests = {};
  172.         }
  173.     };
  174.  
  175.     var send = function() {
  176.         timer = null;
  177.         messager.send({
  178.             what: 'filterRequests',
  179.             pageURL: window.location.href,
  180.             pageHostname: window.location.hostname,
  181.             requests: newRequests
  182.         }, onProcessed);
  183.         newRequests = [];
  184.     };
  185.  
  186.     var process = function(delay) {
  187.         if ( newRequests.length === 0 ) {
  188.             return;
  189.         }
  190.         if ( delay === 0 ) {
  191.             clearTimeout(timer);
  192.             send();
  193.         } else if ( timer === null ) {
  194.             timer = vAPI.setTimeout(send, delay || 20);
  195.         }
  196.     };
  197.  
  198.     // If needed eventually, we could listen to `src` attribute changes
  199.     // for iframes.
  200.  
  201.     var add = function(target) {
  202.         var tagName = target.localName;
  203.         var prop = src1stProps[tagName];
  204.         if ( prop === undefined ) {
  205.             return;
  206.         }
  207.         // https://github.com/chrisaljoudi/uBlock/issues/174
  208.         // Do not remove fragment from src URL
  209.         var src = target[prop];
  210.         if ( typeof src !== 'string' || src.length === 0 ) {
  211.             prop = src2ndProps[tagName];
  212.             if ( prop === undefined ) {
  213.                 return;
  214.             }
  215.             src = target[prop];
  216.             if ( typeof src !== 'string' || src.length === 0 ) {
  217.                 return;
  218.             }
  219.         }
  220.         if ( src.lastIndexOf('http', 0) !== 0 ) {
  221.             return;
  222.         }
  223.         var req = new PendingRequest(target, tagName, prop);
  224.         newRequests.push(new BouncingRequest(req.id, tagName, src));
  225.     };
  226.  
  227.     var iframeSourceModified = function(mutations) {
  228.         var i = mutations.length;
  229.         while ( i-- ) {
  230.             addIFrame(mutations[i].target, true);
  231.         }
  232.         process();
  233.     };
  234.     var iframeSourceObserver = new MutationObserver(iframeSourceModified);
  235.     var iframeSourceObserverOptions = {
  236.         attributes: true,
  237.         attributeFilter: [ 'src' ]
  238.     };
  239.  
  240.     var addIFrame = function(iframe, dontObserve) {
  241.         // https://github.com/gorhill/uBlock/issues/162
  242.         // Be prepared to deal with possible change of src attribute.
  243.         if ( dontObserve !== true ) {
  244.             iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
  245.         }
  246.  
  247.         var src = iframe.src;
  248.         if ( src === '' || typeof src !== 'string' ) {
  249.             return;
  250.         }
  251.         if ( src.lastIndexOf('http', 0) !== 0 ) {
  252.             return;
  253.         }
  254.         var req = new PendingRequest(iframe, 'iframe', 'src');
  255.         newRequests.push(new BouncingRequest(req.id, 'iframe', src));
  256.     };
  257.  
  258.     var iframesFromNode = function(node) {
  259.         if ( node.localName === 'iframe' ) {
  260.             addIFrame(node);
  261.         }
  262.         var iframes = node.getElementsByTagName('iframe');
  263.         var i = iframes.length;
  264.         while ( i-- ) {
  265.             addIFrame(iframes[i]);
  266.         }
  267.         process();
  268.     };
  269.  
  270.     return {
  271.         add: add,
  272.         addIFrame: addIFrame,
  273.         iframesFromNode: iframesFromNode,
  274.         process: process
  275.     };
  276. })();
  277.  
  278. /******************************************************************************/
  279. /******************************************************************************/
  280.  
  281. // Cosmetic filters
  282.  
  283. (function() {
  284.     if ( vAPI.skipCosmeticFiltering ) {
  285.         //console.debug('Abort cosmetic filtering');
  286.         return;
  287.     }
  288.  
  289.     //console.debug('Start cosmetic filtering');
  290.  
  291.     //var timer = window.performance || Date;
  292.     //var tStart = timer.now();
  293.  
  294.     // https://github.com/chrisaljoudi/uBlock/issues/789
  295.     // https://github.com/gorhill/uBlock/issues/873
  296.     // Be sure that our style tags used for cosmetic filtering are still applied.
  297.     var checkStyleTags = function() {
  298.         var doc = document,
  299.             html = doc.documentElement,
  300.             head = doc.head,
  301.             newParent = html || head;
  302.         if ( newParent === null ) {
  303.             return;
  304.         }
  305.         var styles = vAPI.styles || [],
  306.             style, oldParent;
  307.         for ( var i = 0; i < styles.length; i++ ) {
  308.             style = styles[i];
  309.             oldParent = style.parentNode;
  310.             if ( oldParent !== head && oldParent !== html ) {
  311.                 newParent.appendChild(style);
  312.             }
  313.         }
  314.     };
  315.     checkStyleTags();
  316.  
  317.     var queriedSelectors = {};
  318.     var injectedSelectors = {};
  319.     var lowGenericSelectors = [];
  320.     var highGenerics = null;
  321.     var contextNodes = [document];
  322.     var nullArray = { push: function(){} };
  323.  
  324.     var retrieveGenericSelectors = function() {
  325.         if ( lowGenericSelectors.length !== 0 || highGenerics === null ) {
  326.             //console.log('µBlock> ABP cosmetic filters: retrieving CSS rules using %d selectors', lowGenericSelectors.length);
  327.             messager.send({
  328.                     what: 'retrieveGenericCosmeticSelectors',
  329.                     pageURL: window.location.href,
  330.                     selectors: lowGenericSelectors,
  331.                     firstSurvey: highGenerics === null
  332.                 },
  333.                 retrieveHandler
  334.             );
  335.             // https://github.com/chrisaljoudi/uBlock/issues/452
  336.             retrieveHandler = nextRetrieveHandler;
  337.         } else {
  338.             nextRetrieveHandler(null);
  339.         }
  340.         lowGenericSelectors = [];
  341.     };
  342.  
  343.     // https://github.com/chrisaljoudi/uBlock/issues/452
  344.     // This needs to be executed *after* the response from our query is
  345.     // received, not at `DOMContentLoaded` time, or else there is a good
  346.     // likeliness to outrun contentscript-start.js, which may still be waiting
  347.     // on a response from its own query.
  348.     var firstRetrieveHandler = function(response) {
  349.         // https://github.com/chrisaljoudi/uBlock/issues/158
  350.         // Ensure injected styles are enforced
  351.         // rhill 2014-11-16: not sure this is needed anymore. Test case in
  352.         //  above issue was fine without the line below..
  353.         var selectors = vAPI.hideCosmeticFilters;
  354.         if ( typeof selectors === 'object' ) {
  355.             injectedSelectors = selectors;
  356.             hideElements(Object.keys(selectors));
  357.         }
  358.         // Add exception filters into injected filters collection, in order
  359.         // to force them to be seen as "already injected".
  360.         selectors = vAPI.donthideCosmeticFilters;
  361.         if ( typeof selectors === 'object' ) {
  362.             for ( var selector in selectors ) {
  363.                 if ( selectors.hasOwnProperty(selector) ) {
  364.                     injectedSelectors[selector] = true;
  365.                 }
  366.             }
  367.         }
  368.         // Flush dead code from memory
  369.         firstRetrieveHandler = null;
  370.  
  371.         // These are sent only once
  372.         var result = response && response.result;
  373.         if ( result ) {
  374.             if ( result.highGenerics ) {
  375.                 highGenerics = result.highGenerics;
  376.             }
  377.             if ( result.donthide ) {
  378.                 processLowGenerics(result.donthide, nullArray);
  379.             }
  380.         }
  381.  
  382.         nextRetrieveHandler(response);
  383.     };
  384.  
  385.     var nextRetrieveHandler = function(response) {
  386.         // https://github.com/gorhill/uMatrix/issues/144
  387.         if ( response && response.shutdown ) {
  388.             vAPI.shutdown.exec();
  389.             return;
  390.         }
  391.  
  392.         //var tStart = timer.now();
  393.         //console.debug('µBlock> contextNodes = %o', contextNodes);
  394.         var result = response && response.result;
  395.         var hideSelectors = [];
  396.  
  397.         if ( result && result.hide.length ) {
  398.             processLowGenerics(result.hide, hideSelectors);
  399.         }
  400.         if ( highGenerics ) {
  401.             if ( highGenerics.hideLowCount ) {
  402.                 processHighLowGenerics(highGenerics.hideLow, hideSelectors);
  403.             }
  404.             if ( highGenerics.hideMediumCount ) {
  405.                 processHighMediumGenerics(highGenerics.hideMedium, hideSelectors);
  406.             }
  407.             if ( highGenerics.hideHighCount ) {
  408.                 processHighHighGenericsAsync();
  409.             }
  410.         }
  411.         if ( hideSelectors.length !== 0 ) {
  412.             addStyleTag(hideSelectors);
  413.         }
  414.         contextNodes.length = 0;
  415.         //console.debug('%f: uBlock: CSS injection time', timer.now() - tStart);
  416.     };
  417.  
  418.     var retrieveHandler = firstRetrieveHandler;
  419.  
  420.     // Ensure elements matching a set of selectors are visually removed
  421.     // from the page, by:
  422.     // - Modifying the style property on the elements themselves
  423.     // - Injecting a style tag
  424.  
  425.     var addStyleTag = function(selectors) {
  426.         var selectorStr = selectors.join(',\n');
  427.         var style = document.createElement('style');
  428.         // The linefeed before the style block is very important: do no remove!
  429.         style.appendChild(document.createTextNode(selectorStr + '\n{display:none !important;}'));
  430.         var parent = document.head || document.documentElement;
  431.         if ( parent ) {
  432.             parent.appendChild(style);
  433.             vAPI.styles.push(style);
  434.         }
  435.         hideElements(selectorStr);
  436.         messager.send({
  437.             what: 'cosmeticFiltersInjected',
  438.             type: 'cosmetic',
  439.             hostname: window.location.hostname,
  440.             selectors: selectors
  441.         });
  442.         //console.debug('µBlock> generic cosmetic filters: injecting %d CSS rules:', selectors.length, text);
  443.     };
  444.  
  445.     var hideElements = (function() {
  446.         if ( document.body === null ) {
  447.             return function() {};
  448.         }
  449.         if ( document.body.shadowRoot === undefined ) {
  450.             return function(selectors) {
  451.                 // https://github.com/chrisaljoudi/uBlock/issues/207
  452.                 // Do not call querySelectorAll() using invalid CSS selectors
  453.                 if ( selectors.length === 0 ) { return; }
  454.                 var elems = document.querySelectorAll(selectors);
  455.                 var i = elems.length;
  456.                 if ( i === 0 ) { return; }
  457.                 // https://github.com/chrisaljoudi/uBlock/issues/158
  458.                 // Using CSSStyleDeclaration.setProperty is more reliable
  459.                 while ( i-- ) {
  460.                     elems[i].style.setProperty('display', 'none', 'important');
  461.                 }
  462.             };
  463.         }
  464.         return function(selectors) {
  465.             if ( selectors.length === 0 ) { return; }
  466.             var elems = document.querySelectorAll(selectors);
  467.             var i = elems.length;
  468.             if ( i === 0 ) { return; }
  469.             // https://github.com/gorhill/uBlock/issues/435
  470.             // Using shadow content so that we do not have to modify style
  471.             // attribute.
  472.             var sessionId = vAPI.sessionId;
  473.             var elem, shadow;
  474.             while ( i-- ) {
  475.                 elem = elems[i];
  476.                 shadow = elem.shadowRoot;
  477.                 // https://www.chromestatus.com/features/4668884095336448
  478.                 // "Multiple shadow roots is being deprecated."
  479.                 if ( shadow !== null ) {
  480.                     if ( shadow.className !== sessionId ) {
  481.                         elem.style.setProperty('display', 'none', 'important');
  482.                     }
  483.                     continue;
  484.                 }
  485.                 // https://github.com/gorhill/uBlock/pull/555
  486.                 // Not all nodes can be shadowed:
  487.                 //   https://github.com/w3c/webcomponents/issues/102
  488.                 // https://github.com/gorhill/uBlock/issues/762
  489.                 // Remove display style that might get in the way of the shadow
  490.                 // node doing its magic.
  491.                 try {
  492.                     shadow = elem.createShadowRoot();
  493.                     shadow.className = sessionId;
  494.                     elem.style.removeProperty('display');
  495.                 } catch (ex) {
  496.                     elem.style.setProperty('display', 'none', 'important');
  497.                 }
  498.             }
  499.         };
  500.     })();
  501.  
  502.     // Extract and return the staged nodes which (may) match the selectors.
  503.  
  504.     var selectNodes = function(selector) {
  505.         var targetNodes = [];
  506.         var i = contextNodes.length;
  507.         var node, nodeList, j;
  508.         var doc = document;
  509.         while ( i-- ) {
  510.             node = contextNodes[i];
  511.             if ( node === doc ) {
  512.                 return doc.querySelectorAll(selector);
  513.             }
  514.             targetNodes.push(node);
  515.             nodeList = node.querySelectorAll(selector);
  516.             j = nodeList.length;
  517.             while ( j-- ) {
  518.                 targetNodes.push(nodeList[j]);
  519.             }
  520.         }
  521.         return targetNodes;
  522.     };
  523.  
  524.     // Low generics:
  525.     // - [id]
  526.     // - [class]
  527.  
  528.     var processLowGenerics = function(generics, out) {
  529.         var i = generics.length;
  530.         var selector;
  531.         while ( i-- ) {
  532.             selector = generics[i];
  533.             if ( injectedSelectors.hasOwnProperty(selector) ) {
  534.                 continue;
  535.             }
  536.             injectedSelectors[selector] = true;
  537.             out.push(selector);
  538.         }
  539.     };
  540.  
  541.     // High-low generics:
  542.     // - [alt="..."]
  543.     // - [title="..."]
  544.  
  545.     var processHighLowGenerics = function(generics, out) {
  546.         var attrs = ['title', 'alt'];
  547.         var attr, attrValue, nodeList, iNode, node;
  548.         var selector;
  549.         while ( (attr = attrs.pop()) ) {
  550.             nodeList = selectNodes('[' + attr + ']');
  551.             iNode = nodeList.length;
  552.             while ( iNode-- ) {
  553.                 node = nodeList[iNode];
  554.                 attrValue = node.getAttribute(attr);
  555.                 if ( !attrValue ) { continue; }
  556.                 // Candidate 1 = generic form
  557.                 // If generic form is injected, no need to process the specific
  558.                 // form, as the generic will affect all related specific forms
  559.                 selector = '[' + attr + '="' + attrValue + '"]';
  560.                 if ( generics.hasOwnProperty(selector) ) {
  561.                     if ( injectedSelectors.hasOwnProperty(selector) === false ) {
  562.                         injectedSelectors[selector] = true;
  563.                         out.push(selector);
  564.                         continue;
  565.                     }
  566.                 }
  567.                 // Candidate 2 = specific form
  568.                 selector = node.localName + selector;
  569.                 if ( generics.hasOwnProperty(selector) ) {
  570.                     if ( injectedSelectors.hasOwnProperty(selector) === false ) {
  571.                         injectedSelectors[selector] = true;
  572.                         out.push(selector);
  573.                     }
  574.                 }
  575.             }
  576.         }
  577.     };
  578.  
  579.     // High-medium generics:
  580.     // - [href^="http"]
  581.  
  582.     var processHighMediumGenerics = function(generics, out) {
  583.         var doc = document;
  584.         var i = contextNodes.length;
  585.         var aa = [ null ];
  586.         var node, nodes;
  587.         while ( i-- ) {
  588.             node = contextNodes[i];
  589.             if ( node.localName === 'a' ) {
  590.                 aa[0] = node;
  591.                 processHighMediumGenericsForNodes(aa, generics, out);
  592.             }
  593.             nodes = node.getElementsByTagName('a');
  594.             if ( nodes.length === 0 ) { continue; }
  595.             processHighMediumGenericsForNodes(nodes, generics, out);
  596.             if ( node === doc ) {
  597.                 break;
  598.             }
  599.         }
  600.     };
  601.  
  602.     var processHighMediumGenericsForNodes = function(nodes, generics, out) {
  603.         var i = nodes.length;
  604.         var node, href, pos, hash, selectors, j, selector;
  605.         var aa = [ '' ];
  606.         while ( i-- ) {
  607.             node = nodes[i];
  608.             href = node.getAttribute('href');
  609.             if ( !href ) { continue; }
  610.             pos = href.indexOf('://');
  611.             if ( pos === -1 ) { continue; }
  612.             hash = href.slice(pos + 3, pos + 11);
  613.             selectors = generics[hash];
  614.             if ( selectors === undefined ) { continue; }
  615.             // A string.
  616.             if ( typeof selectors === 'string' ) {
  617.                 aa[0] = selectors;
  618.                 selectors = aa;
  619.             }
  620.             // An array of strings.
  621.             j = selectors.length;
  622.             while ( j-- ) {
  623.                 selector = selectors[j];
  624.                 if (
  625.                     href.lastIndexOf(selector.slice(8, -2), 0) === 0 &&
  626.                     injectedSelectors.hasOwnProperty(selector) === false
  627.                 ) {
  628.                     injectedSelectors[selector] = true;
  629.                     out.push(selector);
  630.                 }
  631.             }
  632.         }
  633.     };
  634.  
  635.     // High-high generics are *very costly* to process, so we will coalesce
  636.     // requests to process high-high generics into as few requests as possible.
  637.     // The gain is *significant* on bloated pages.
  638.  
  639.     var processHighHighGenericsMisses = 8;
  640.     var processHighHighGenericsTimer = null;
  641.  
  642.     var processHighHighGenerics = function() {
  643.         processHighHighGenericsTimer = null;
  644.         if ( highGenerics.hideHigh === '' ) {
  645.             return;
  646.         }
  647.         if ( injectedSelectors.hasOwnProperty('{{highHighGenerics}}') ) {
  648.             return;
  649.         }
  650.         // When there are too many misses for these highly generic CSS rules,
  651.         // we will just give up on looking whether they need to be injected.
  652.         if ( document.querySelector(highGenerics.hideHigh) === null ) {
  653.             processHighHighGenericsMisses -= 1;
  654.             if ( processHighHighGenericsMisses === 0 ) {
  655.                 injectedSelectors['{{highHighGenerics}}'] = true;
  656.             }
  657.             return;
  658.         }
  659.         injectedSelectors['{{highHighGenerics}}'] = true;
  660.         // We need to filter out possible exception cosmetic filters from
  661.         // high-high generics selectors.
  662.         var selectors = highGenerics.hideHigh.split(',\n');
  663.         var i = selectors.length;
  664.         var selector;
  665.         while ( i-- ) {
  666.             selector = selectors[i];
  667.             if ( injectedSelectors.hasOwnProperty(selector) ) {
  668.                 selectors.splice(i, 1);
  669.             } else {
  670.                 injectedSelectors[selector] = true;
  671.             }
  672.         }
  673.         if ( selectors.length !== 0 ) {
  674.             addStyleTag(selectors);
  675.         }
  676.     };
  677.  
  678.     var processHighHighGenericsAsync = function() {
  679.         if ( processHighHighGenericsTimer !== null ) {
  680.             clearTimeout(processHighHighGenericsTimer);
  681.         }
  682.         processHighHighGenericsTimer = vAPI.setTimeout(processHighHighGenerics, 300);
  683.     };
  684.  
  685.     // Extract all ids: these will be passed to the cosmetic filtering
  686.     // engine, and in return we will obtain only the relevant CSS selectors.
  687.  
  688.     var idsFromNodeList = function(nodes) {
  689.         if ( !nodes || !nodes.length ) {
  690.             return;
  691.         }
  692.         var qq = queriedSelectors;
  693.         var ll = lowGenericSelectors;
  694.         var node, v;
  695.         var i = nodes.length;
  696.         while ( i-- ) {
  697.             node = nodes[i];
  698.             if ( node.nodeType !== 1 ) { continue; }
  699.             v = node.id;
  700.             if ( typeof v !== 'string' ) { continue; }
  701.             v = v.trim();
  702.             if ( v === '' ) { continue; }
  703.             v = '#' + v;
  704.             if ( qq.hasOwnProperty(v) ) { continue; }
  705.             ll.push(v);
  706.             qq[v] = true;
  707.         }
  708.     };
  709.  
  710.     // Extract all classes: these will be passed to the cosmetic filtering
  711.     // engine, and in return we will obtain only the relevant CSS selectors.
  712.  
  713.     // https://github.com/gorhill/uBlock/issues/672
  714.     // http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
  715.     // http://jsperf.com/enumerate-classes/6
  716.  
  717.     var classesFromNodeList = function(nodes) {
  718.         if ( !nodes || !nodes.length ) {
  719.             return;
  720.         }
  721.  
  722.         var qq = queriedSelectors;
  723.         var ll = lowGenericSelectors;
  724.         var v, vv, len, c, beg, end;
  725.         var i = nodes.length;
  726.  
  727.         while ( i-- ) {
  728.             vv = nodes[i].className;
  729.             if ( typeof vv !== 'string' ) { continue; }
  730.             len = vv.length;
  731.             beg = 0;
  732.             for (;;) {
  733.                 // Skip whitespaces
  734.                 while ( beg !== len ) {
  735.                     c = vv.charCodeAt(beg);
  736.                     if ( c !== 0x20 && (c > 0x0D || c < 0x09) ) { break; }
  737.                     beg++;
  738.                 }
  739.                 if ( beg === len ) { break; }
  740.                 end = beg + 1;
  741.                 // Skip non-whitespaces
  742.                 while ( end !== len ) {
  743.                     c = vv.charCodeAt(end);
  744.                     if ( c === 0x20 || (c <= 0x0D && c >= 0x09) ) { break; }
  745.                     end++;
  746.                 }
  747.                 v = '.' + vv.slice(beg, end);
  748.                 if ( qq.hasOwnProperty(v) === false ) {
  749.                     ll.push(v);
  750.                     qq[v] = true;
  751.                 }
  752.                 if ( end === len ) { break; }
  753.                 beg = end + 1;
  754.             }
  755.         }
  756.     };
  757.  
  758.     // Start cosmetic filtering.
  759.  
  760.     idsFromNodeList(document.querySelectorAll('[id]'));
  761.     classesFromNodeList(document.querySelectorAll('[class]'));
  762.     retrieveGenericSelectors();
  763.  
  764.     //console.debug('%f: uBlock: survey time', timer.now() - tStart);
  765.  
  766.     // Below this point is the code which takes care to observe changes in
  767.     // the page and to add if needed relevant CSS rules as a result of the
  768.     // changes.
  769.  
  770.     // Observe changes in the DOM only if...
  771.     // - there is a document.body
  772.     // - there is at least one `script` tag
  773.     if ( !document.body || !document.querySelector('script') ) {
  774.         return;
  775.     }
  776.  
  777.     // https://github.com/chrisaljoudi/uBlock/issues/618
  778.     // Following is to observe dynamically added iframes:
  779.     // - On Firefox, the iframes fails to fire a `load` event
  780.  
  781.     var ignoreTags = {
  782.         'link': true,
  783.         'script': true,
  784.         'style': true
  785.     };
  786.  
  787.     // Added node lists will be cumulated here before being processed
  788.     var addedNodeLists = [];
  789.     var addedNodeListsTimer = null;
  790.     var removedNodeListsTimer = null;
  791.     var collapser = uBlockCollapser;
  792.  
  793.     // The `cosmeticFiltersActivated` message is required: a new element could
  794.     // be matching an already injected but otherwise inactive cosmetic filter.
  795.     // This means the already injected cosmetic filter become active (has an
  796.     // effect on the document), and thus must be logged if needed.
  797.     var addedNodesHandler = function() {
  798.         addedNodeListsTimer = null;
  799.         var nodeList, iNode, node;
  800.         while ( (nodeList = addedNodeLists.pop()) ) {
  801.             iNode = nodeList.length;
  802.             while ( iNode-- ) {
  803.                 node = nodeList[iNode];
  804.                 if ( node.nodeType !== 1 ) {
  805.                     continue;
  806.                 }
  807.                 if ( ignoreTags.hasOwnProperty(node.localName) ) {
  808.                     continue;
  809.                 }
  810.                 contextNodes.push(node);
  811.                 collapser.iframesFromNode(node);
  812.             }
  813.         }
  814.         if ( contextNodes.length !== 0 ) {
  815.             idsFromNodeList(selectNodes('[id]'));
  816.             classesFromNodeList(selectNodes('[class]'));
  817.             retrieveGenericSelectors();
  818.             messager.send({ what: 'cosmeticFiltersActivated' });
  819.         }
  820.     };
  821.  
  822.     // https://github.com/gorhill/uBlock/issues/873
  823.     // This will ensure our style elements will stay in the DOM.
  824.     var removedNodesHandler = function() {
  825.         removedNodeListsTimer = null;
  826.         checkStyleTags();
  827.     };
  828.  
  829.     // https://github.com/chrisaljoudi/uBlock/issues/205
  830.     // Do not handle added node directly from within mutation observer.
  831.     // I arbitrarily chose 100 ms for now: I have to compromise between the
  832.     // overhead of processing too few nodes too often and the delay of many
  833.     // nodes less often.
  834.     var domLayoutChanged = function(mutations) {
  835.         var removedNodeLists = false;
  836.         var iMutation = mutations.length;
  837.         var nodeList, mutation;
  838.         while ( iMutation-- ) {
  839.             mutation = mutations[iMutation];
  840.             nodeList = mutation.addedNodes;
  841.             if ( nodeList.length !== 0 ) {
  842.                 addedNodeLists.push(nodeList);
  843.             }
  844.             if ( mutation.removedNodes.length !== 0 ) {
  845.                 removedNodeLists = true;
  846.             }
  847.         }
  848.         if ( addedNodeLists.length !== 0 && addedNodeListsTimer === null ) {
  849.             addedNodeListsTimer = vAPI.setTimeout(addedNodesHandler, 100);
  850.         }
  851.         if ( removedNodeLists && removedNodeListsTimer === null ) {
  852.             removedNodeListsTimer = vAPI.setTimeout(removedNodesHandler, 100);
  853.         }
  854.     };
  855.  
  856.     //console.debug('Starts cosmetic filtering\'s mutations observer');
  857.  
  858.     // https://github.com/gorhill/httpswitchboard/issues/176
  859.     var domLayoutObserver = new MutationObserver(domLayoutChanged);
  860.     domLayoutObserver.observe(document.body, {
  861.         childList: true,
  862.         subtree: true
  863.     });
  864.  
  865.     // https://github.com/gorhill/uMatrix/issues/144
  866.     vAPI.shutdown.add(function() {
  867.         domLayoutObserver.disconnect();
  868.         if ( addedNodeListsTimer !== null ) {
  869.             clearTimeout(addedNodeListsTimer);
  870.         }
  871.     });
  872. })();
  873.  
  874. /******************************************************************************/
  875. /******************************************************************************/
  876.  
  877. // Permanent
  878.  
  879. // Listener to collapse blocked resources.
  880. // - Future requests not blocked yet
  881. // - Elements dynamically added to the page
  882. // - Elements which resource URL changes
  883.  
  884. (function() {
  885.     var onResourceFailed = function(ev) {
  886.         //console.debug('onResourceFailed(%o)', ev);
  887.         uBlockCollapser.add(ev.target);
  888.         uBlockCollapser.process();
  889.     };
  890.     document.addEventListener('error', onResourceFailed, true);
  891.  
  892.     // https://github.com/gorhill/uMatrix/issues/144
  893.     vAPI.shutdown.add(function() {
  894.         document.removeEventListener('error', onResourceFailed, true);
  895.     });
  896. })();
  897.  
  898. /******************************************************************************/
  899. /******************************************************************************/
  900.  
  901. // https://github.com/chrisaljoudi/uBlock/issues/7
  902. // Executed only once.
  903. // Preferring getElementsByTagName over querySelectorAll:
  904. //   http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
  905.  
  906. (function() {
  907.     var collapser = uBlockCollapser;
  908.     var elems, i, elem;
  909.  
  910.     elems = document.getElementsByTagName('embed');
  911.     i = elems.length;
  912.     while ( i-- ) {
  913.         collapser.add(elems[i]);
  914.     }
  915.  
  916.     elems = document.getElementsByTagName('object');
  917.     i = elems.length;
  918.     while ( i-- ) {
  919.         collapser.add(elems[i]);
  920.     }
  921.  
  922.     elems = document.getElementsByTagName('img');
  923.     i = elems.length;
  924.     while ( i-- ) {
  925.         elem = elems[i];
  926.         if ( elem.complete ) {
  927.             collapser.add(elem);
  928.         }
  929.     }
  930.  
  931.     elems = document.getElementsByTagName('iframe');
  932.     i = elems.length;
  933.     while ( i-- ) {
  934.         collapser.addIFrame(elems[i]);
  935.     }
  936.  
  937.     collapser.process(0);
  938. })();
  939.  
  940. /******************************************************************************/
  941. /******************************************************************************/
  942.  
  943. // To send mouse coordinates to context menu handler, as the chrome API fails
  944. // to provide the mouse position to context menu listeners.
  945. // This could be inserted in its own content script, but it's so simple that
  946. // I feel it's not worth the overhead.
  947.  
  948. // Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
  949.  
  950. (function() {
  951.     if ( window !== window.top ) {
  952.         return;
  953.     }
  954.     var onMouseClick = function(ev) {
  955.         var elem = ev.target;
  956.         while ( elem !== null && elem.localName !== 'a' ) {
  957.             elem = elem.parentElement;
  958.         }
  959.         messager.send({
  960.             what: 'mouseClick',
  961.             x: ev.clientX,
  962.             y: ev.clientY,
  963.             url: elem !== null ? elem.href : ''
  964.         });
  965.     };
  966.  
  967.     window.addEventListener('mousedown', onMouseClick, true);
  968.  
  969.     // https://github.com/gorhill/uMatrix/issues/144
  970.     vAPI.shutdown.add(function() {
  971.         document.removeEventListener('mousedown', onMouseClick, true);
  972.     });
  973. })();
  974.  
  975. /******************************************************************************/
  976. /******************************************************************************/
  977.  
  978. })();
  979.  
  980. /******************************************************************************/
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement