| LEFT | RIGHT | 
|    1 // We are currently limited to ECMAScript 5 in this file, because it is being |    1 /* | 
|    2 // used in the browser tests. See https://issues.adblockplus.org/ticket/4796 |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|    3  |    3  * Copyright (C) 2006-2017 eyeo GmbH | 
|    4 var propertySelectorRegExp = /\[\-abp\-properties=(["'])([^"']+)\1\]/; |    4  * | 
|    5 var pseudoClassHasSelectorRegExp = /:has\((.*)\)/; |    5  * Adblock Plus is free software: you can redistribute it and/or modify | 
 |    6  * it under the terms of the GNU General Public License version 3 as | 
 |    7  * published by the Free Software Foundation. | 
 |    8  * | 
 |    9  * Adblock Plus is distributed in the hope that it will be useful, | 
 |   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |   12  * GNU General Public License for more details. | 
 |   13  * | 
 |   14  * You should have received a copy of the GNU General Public License | 
 |   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
 |   16  */ | 
 |   17  | 
 |   18 /* globals filterToRegExp */ | 
 |   19  | 
 |   20 "use strict"; | 
 |   21  | 
 |   22 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 
 |   23  | 
 |   24 let reportError = () => {}; | 
|    6  |   25  | 
|    7 function splitSelector(selector) |   26 function splitSelector(selector) | 
|    8 { |   27 { | 
|    9   if (selector.indexOf(",") == -1) |   28   if (selector.indexOf(",") == -1) | 
|   10     return [selector]; |   29     return [selector]; | 
|   11  |   30  | 
|   12   var selectors = []; |   31   let selectors = []; | 
|   13   var start = 0; |   32   let start = 0; | 
|   14   var level = 0; |   33   let level = 0; | 
|   15   var sep = ""; |   34   let sep = ""; | 
|   16  |   35  | 
|   17   for (var i = 0; i < selector.length; i++) |   36   for (let i = 0; i < selector.length; i++) | 
|   18   { |   37   { | 
|   19     var chr = selector[i]; |   38     let chr = selector[i]; | 
|   20  |   39  | 
|   21     if (chr == "\\")        // ignore escaped characters |   40     if (chr == "\\")        // ignore escaped characters | 
|   22       i++; |   41       i++; | 
|   23     else if (chr == sep)    // don't split within quoted text |   42     else if (chr == sep)    // don't split within quoted text | 
|   24       sep = "";             // e.g. [attr=","] |   43       sep = "";             // e.g. [attr=","] | 
|   25     else if (sep == "") |   44     else if (sep == "") | 
|   26     { |   45     { | 
|   27       if (chr == '"' || chr == "'") |   46       if (chr == '"' || chr == "'") | 
|   28         sep = chr; |   47         sep = chr; | 
|   29       else if (chr == "(")  // don't split between parentheses |   48       else if (chr == "(")  // don't split between parentheses | 
|   30         level++;            // e.g. :matches(div,span) |   49         level++;            // e.g. :matches(div,span) | 
|   31       else if (chr == ")") |   50       else if (chr == ")") | 
|   32         level = Math.max(0, level - 1); |   51         level = Math.max(0, level - 1); | 
|   33       else if (chr == "," && level == 0) |   52       else if (chr == "," && level == 0) | 
|   34       { |   53       { | 
|   35         selectors.push(selector.substring(start, i)); |   54         selectors.push(selector.substring(start, i)); | 
|   36         start = i + 1; |   55         start = i + 1; | 
|   37       } |   56       } | 
|   38     } |   57     } | 
|   39   } |   58   } | 
|   40  |   59  | 
|   41   selectors.push(selector.substring(start)); |   60   selectors.push(selector.substring(start)); | 
|   42   return selectors; |   61   return selectors; | 
|   43 } |   62 } | 
|   44  |   63  | 
|   45 // matcher for the pseudo CSS4 class :has |   64 /** Return position of node from parent. | 
|   46 // For those browser that don't have it yet. |   65  * @param {Node} node the node to find the position of. | 
|   47 function PseudoHasMatcher(selector) |   66  * @return {number} One-based index like for :nth-child(), or 0 on error. | 
|   48 { |   67  */ | 
|   49   this.hasSelector = selector; |   68 function positionInParent(node) | 
|   50 } |   69 { | 
|   51  |   70   let {children} = node.parentNode; | 
|   52 PseudoHasMatcher.prototype = { |   71   for (let i = 0; i < children.length; i++) | 
|   53   match: function(elem) |   72     if (children[i] == node) | 
|   54   { |   73       return i + 1; | 
|   55     var matches = []; |   74   return 0; | 
|   56     // look up for all elements that match the :has(). |   75 } | 
|   57     var children = elem.children; |   76  | 
|   58     for (var i = 0; i < children.length; i++) |   77 function makeSelector(node, selector) | 
|   59     { |   78 { | 
|   60       var hasElem = elem.querySelector(this.hasSelector); |   79   if (!node.parentElement) | 
|   61       if (hasElem != null) |   80   { | 
|   62       { |   81     let newSelector = ":root"; | 
|   63         matches.push(hasElem); |   82     if (selector) | 
|   64       } |   83       newSelector += " > " + selector; | 
|   65     } |   84     return newSelector; | 
|   66     return matches; |   85   } | 
 |   86   let idx = positionInParent(node); | 
 |   87   if (idx > 0) | 
 |   88   { | 
 |   89     let newSelector = `${node.tagName}:nth-child(${idx})`; | 
 |   90     if (selector) | 
 |   91       newSelector += " > " + selector; | 
 |   92     return makeSelector(node.parentElement, newSelector); | 
 |   93   } | 
 |   94  | 
 |   95   return selector; | 
 |   96 } | 
 |   97  | 
 |   98 function parseSelectorContent(content, startIndex) | 
 |   99 { | 
 |  100   let parens = 1; | 
 |  101   let quote = null; | 
 |  102   let i = startIndex; | 
 |  103   for (; i < content.length; i++) | 
 |  104   { | 
 |  105     let c = content[i]; | 
 |  106     if (c == "\\") | 
 |  107     { | 
 |  108       // Ignore escaped characters | 
 |  109       i++; | 
 |  110     } | 
 |  111     else if (quote) | 
 |  112     { | 
 |  113       if (c == quote) | 
 |  114         quote = null; | 
 |  115     } | 
 |  116     else if (c == "'" || c == '"') | 
 |  117       quote = c; | 
 |  118     else if (c == "(") | 
 |  119       parens++; | 
 |  120     else if (c == ")") | 
 |  121     { | 
 |  122       parens--; | 
 |  123       if (parens == 0) | 
 |  124         break; | 
 |  125     } | 
 |  126   } | 
 |  127  | 
 |  128   if (parens > 0) | 
 |  129     return null; | 
 |  130   return {text: content.substring(startIndex, i), end: i}; | 
 |  131 } | 
 |  132  | 
 |  133 /** Parse the selector | 
 |  134  * @param {string} selector the selector to parse | 
 |  135  * @return {Object} selectors is an array of objects, | 
 |  136  * or null in case of errors. hide is true if we'll hide | 
 |  137  * elements instead of styles.. | 
 |  138  */ | 
 |  139 function parseSelector(selector) | 
 |  140 { | 
 |  141   if (selector.length == 0) | 
 |  142     return []; | 
 |  143  | 
 |  144   let match = abpSelectorRegexp.exec(selector); | 
 |  145   if (!match) | 
 |  146     return [new PlainSelector(selector)]; | 
 |  147  | 
 |  148   let selectors = []; | 
 |  149   if (match.index > 0) | 
 |  150     selectors.push(new PlainSelector(selector.substr(0, match.index))); | 
 |  151  | 
 |  152   let startIndex = match.index + match[0].length; | 
 |  153   let content = parseSelectorContent(selector, startIndex); | 
 |  154   if (!content) | 
 |  155   { | 
 |  156     reportError(new SyntaxError("Failed to parse Adblock Plus " + | 
 |  157                                 `selector ${selector}, ` + | 
 |  158                                 "due to unmatched parentheses.")); | 
 |  159     return null; | 
 |  160   } | 
 |  161   if (match[1] == "properties") | 
 |  162     selectors.push(new PropsSelector(content.text)); | 
 |  163   else if (match[1] == "has") | 
 |  164   { | 
 |  165     let hasSelector = new HasSelector(content.text); | 
 |  166     if (!hasSelector.valid()) | 
 |  167       return null; | 
 |  168     selectors.push(hasSelector); | 
 |  169   } | 
 |  170   else | 
 |  171   { | 
 |  172     // this is an error, can't parse selector. | 
 |  173     reportError(new SyntaxError("Failed to parse Adblock Plus " + | 
 |  174                                 `selector ${selector}, invalid ` + | 
 |  175                                 `pseudo-class :-abp-${match[1]}().`)); | 
 |  176     return null; | 
 |  177   } | 
 |  178  | 
 |  179   let suffix = parseSelector(selector.substr(content.end + 1)); | 
 |  180   if (suffix == null) | 
 |  181     return null; | 
 |  182  | 
 |  183   selectors.push(...suffix); | 
 |  184  | 
 |  185   return selectors; | 
 |  186 } | 
 |  187  | 
 |  188 /** Stringified style objects | 
 |  189  * @typedef {Object} StringifiedStyle | 
 |  190  * @property {string} style CSS style represented by a string. | 
 |  191  * @property {string[]} subSelectors selectors the CSS properties apply to. | 
 |  192  */ | 
 |  193  | 
 |  194 /** | 
 |  195  * Produce a string representation of the stylesheet entry. | 
 |  196  * @param {CSSStyleRule} rule the CSS style rule. | 
 |  197  * @return {StringifiedStyle} the stringified style. | 
 |  198  */ | 
 |  199 function stringifyStyle(rule) | 
 |  200 { | 
 |  201   let styles = []; | 
 |  202   for (let i = 0; i < rule.style.length; i++) | 
 |  203   { | 
 |  204     let property = rule.style.item(i); | 
 |  205     let value = rule.style.getPropertyValue(property); | 
 |  206     let priority = rule.style.getPropertyPriority(property); | 
 |  207     styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`); | 
 |  208   } | 
 |  209   styles.sort(); | 
 |  210   return { | 
 |  211     style: styles.join(" "), | 
 |  212     subSelectors: splitSelector(rule.selectorText) | 
 |  213   }; | 
 |  214 } | 
 |  215  | 
 |  216 function* evaluate(chain, index, prefix, subtree, styles) | 
 |  217 { | 
 |  218   if (index >= chain.length) | 
 |  219   { | 
 |  220     yield prefix; | 
 |  221     return; | 
 |  222   } | 
 |  223   for (let [selector, element] of | 
 |  224        chain[index].getSelectors(prefix, subtree, styles)) | 
 |  225     yield* evaluate(chain, index + 1, selector, element, styles); | 
 |  226 } | 
 |  227  | 
 |  228 function PlainSelector(selector) | 
 |  229 { | 
 |  230   this._selector = selector; | 
 |  231 } | 
 |  232  | 
 |  233 PlainSelector.prototype = { | 
 |  234   /** | 
 |  235    * Generator function returning a pair of selector | 
 |  236    * string and subtree. | 
 |  237    * @param {string} prefix the prefix for the selector. | 
 |  238    * @param {Node} subtree the subtree we work on. | 
 |  239    * @param {StringifiedStyle[]} styles the stringified style objects. | 
 |  240    */ | 
 |  241   *getSelectors(prefix, subtree, styles) | 
 |  242   { | 
 |  243     yield [prefix + this._selector, subtree]; | 
|   67   } |  244   } | 
|   68 }; |  245 }; | 
|   69  |  246  | 
|   70 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, hideElement
     sFunc) |  247 const incompletePrefixRegexp = /[\s>+~]$/; | 
 |  248  | 
 |  249 function HasSelector(selector) | 
 |  250 { | 
 |  251   this._innerSelectors = parseSelector(selector); | 
 |  252 } | 
 |  253  | 
 |  254 HasSelector.prototype = { | 
 |  255   requiresHiding: true, | 
 |  256  | 
 |  257   valid() | 
 |  258   { | 
 |  259     return this._innerSelectors != null; | 
 |  260   }, | 
 |  261  | 
 |  262   *getSelectors(prefix, subtree, styles) | 
 |  263   { | 
 |  264     for (let element of this.getElements(prefix, subtree, styles)) | 
 |  265       yield [makeSelector(element, ""), element]; | 
 |  266   }, | 
 |  267  | 
 |  268   /** | 
 |  269    * Generator function returning selected elements. | 
 |  270    * @param {string} prefix the prefix for the selector. | 
 |  271    * @param {Node} subtree the subtree we work on. | 
 |  272    * @param {StringifiedStyle[]} styles the stringified style objects. | 
 |  273    */ | 
 |  274   *getElements(prefix, subtree, styles) | 
 |  275   { | 
 |  276     let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 
 |  277         prefix + "*" : prefix; | 
 |  278     let elements = subtree.querySelectorAll(actualPrefix); | 
 |  279     for (let element of elements) | 
 |  280     { | 
 |  281       let newPrefix = makeSelector(element, ""); | 
 |  282       let iter = evaluate(this._innerSelectors, 0, newPrefix + " ", | 
 |  283                           element, styles); | 
 |  284       for (let selector of iter) | 
 |  285         // we insert a space between the two. It becomes a no-op if selector | 
 |  286         // doesn't have a combinator | 
 |  287         if (subtree.querySelector(selector)) | 
 |  288           yield element; | 
 |  289     } | 
 |  290   } | 
 |  291 }; | 
 |  292  | 
 |  293 function PropsSelector(propertyExpression) | 
 |  294 { | 
 |  295   let regexpString; | 
 |  296   if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 
 |  297       propertyExpression[propertyExpression.length - 1] == "/") | 
 |  298   { | 
 |  299     regexpString = propertyExpression.slice(1, -1) | 
 |  300       .replace("\\x7B ", "{").replace("\\x7D ", "}"); | 
 |  301   } | 
 |  302   else | 
 |  303     regexpString = filterToRegExp(propertyExpression); | 
 |  304  | 
 |  305   this._regexp = new RegExp(regexpString, "i"); | 
 |  306 } | 
 |  307  | 
 |  308 PropsSelector.prototype = { | 
 |  309   *findPropsSelectors(styles, prefix, regexp) | 
 |  310   { | 
 |  311     for (let style of styles) | 
 |  312       if (regexp.test(style.style)) | 
 |  313         for (let subSelector of style.subSelectors) | 
 |  314           yield prefix + subSelector; | 
 |  315   }, | 
 |  316  | 
 |  317   *getSelectors(prefix, subtree, styles) | 
 |  318   { | 
 |  319     for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 
 |  320       yield [selector, subtree]; | 
 |  321   } | 
 |  322 }; | 
 |  323  | 
 |  324 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, | 
 |  325                            hideElemsFunc) | 
|   71 { |  326 { | 
|   72   this.window = window; |  327   this.window = window; | 
|   73   this.getFiltersFunc = getFiltersFunc; |  328   this.getFiltersFunc = getFiltersFunc; | 
|   74   this.addSelectorsFunc = addSelectorsFunc; |  329   this.addSelectorsFunc = addSelectorsFunc; | 
|   75   this.hideElementsFunc = hideElementsFunc; |  330   this.hideElemsFunc = hideElemsFunc; | 
|   76 } |  331 } | 
|   77  |  332  | 
|   78 ElemHideEmulation.prototype = { |  333 ElemHideEmulation.prototype = { | 
|   79   stringifyStyle: function(style) |  334   isSameOrigin(stylesheet) | 
|   80   { |  | 
|   81     var styles = []; |  | 
|   82     for (var i = 0; i < style.length; i++) |  | 
|   83     { |  | 
|   84       var property = style.item(i); |  | 
|   85       var value    = style.getPropertyValue(property); |  | 
|   86       var priority = style.getPropertyPriority(property); |  | 
|   87       styles.push(property + ": " + value + (priority ? " !" + priority : "") + 
     ";"); |  | 
|   88     } |  | 
|   89     styles.sort(); |  | 
|   90     return styles.join(" "); |  | 
|   91   }, |  | 
|   92  |  | 
|   93   isSameOrigin: function(stylesheet) |  | 
|   94   { |  335   { | 
|   95     try |  336     try | 
|   96     { |  337     { | 
|   97       return new URL(stylesheet.href).origin == this.window.location.origin; |  338       return new URL(stylesheet.href).origin == this.window.location.origin; | 
|   98     } |  339     } | 
|   99     catch (e) |  340     catch (e) | 
|  100     { |  341     { | 
|  101       // Invalid URL, assume that it is first-party. |  342       // Invalid URL, assume that it is first-party. | 
|  102       return true; |  343       return true; | 
|  103     } |  344     } | 
|  104   }, |  345   }, | 
|  105  |  346  | 
|  106   findSelectors: function(stylesheet, selectors, filters) |  347   addSelectors(stylesheets) | 
|  107   { |  348   { | 
|  108     // Explicitly ignore third-party stylesheets to ensure consistent behavior |  349     let selectors = []; | 
|  109     // between Firefox and Chrome. |  350     let selectorFilters = []; | 
|  110     if (!this.isSameOrigin(stylesheet)) |  351  | 
|  111       return; |  352     let elements = []; | 
|  112  |  353     let elementFilters = []; | 
|  113     var rules = stylesheet.cssRules; |  354  | 
|  114     if (!rules) |  355     let cssStyles = []; | 
|  115       return; |  356  | 
|  116  |  357     for (let stylesheet of stylesheets) | 
|  117     for (var i = 0; i < rules.length; i++) |  358     { | 
|  118     { |  359       // Explicitly ignore third-party stylesheets to ensure consistent behavior | 
|  119       var rule = rules[i]; |  360       // between Firefox and Chrome. | 
|  120       if (rule.type != rule.STYLE_RULE) |  361       if (!this.isSameOrigin(stylesheet)) | 
|  121         continue; |  362         continue; | 
|  122  |  363  | 
|  123       var style = this.stringifyStyle(rule.style); |  364       let rules = stylesheet.cssRules; | 
|  124       for (var j = 0; j < this.propSelPatterns.length; j++) |  365       if (!rules) | 
 |  366         continue; | 
 |  367  | 
 |  368       for (let rule of rules) | 
|  125       { |  369       { | 
|  126         var pattern = this.propSelPatterns[j]; |  370         if (rule.type != rule.STYLE_RULE) | 
|  127         if (pattern.regexp.test(style)) |  371           continue; | 
 |  372  | 
 |  373         cssStyles.push(stringifyStyle(rule)); | 
 |  374       } | 
 |  375     } | 
 |  376  | 
 |  377     let {document} = this.window; | 
 |  378     for (let pattern of this.patterns) | 
 |  379     { | 
 |  380       for (let selector of evaluate(pattern.selectors, | 
 |  381                                     0, "", document, cssStyles)) | 
 |  382       { | 
 |  383         if (!pattern.selectors.some(s => s.requiresHiding)) | 
|  128         { |  384         { | 
|  129           var subSelectors = splitSelector(rule.selectorText); |  385           selectors.push(selector); | 
|  130           for (var k = 0; k < subSelectors.length; k++) |  386           selectorFilters.push(pattern.text); | 
 |  387         } | 
 |  388         else | 
 |  389         { | 
 |  390           for (let element of document.querySelectorAll(selector)) | 
|  131           { |  391           { | 
|  132             var subSelector = subSelectors[k]; |  392             elements.push(element); | 
|  133             selectors.push(pattern.prefix + subSelector + pattern.suffix); |  393             elementFilters.push(pattern.text); | 
|  134             filters.push(pattern.text); |  | 
|  135           } |  394           } | 
|  136         } |  395         } | 
|  137       } |  396       } | 
|  138     } |  397     } | 
|  139   }, |  398  | 
|  140  |  399     this.addSelectorsFunc(selectors, selectorFilters); | 
|  141   findPseudoClassHasSelectors: function(selectors, filters) |  400     this.hideElemsFunc(elements, elementFilters); | 
|  142   { |  401   }, | 
|  143     for (var i = 0; i < this.pseudoHasPatterns.length; i++) |  402  | 
|  144     { |  403   onLoad(event) | 
|  145       var pattern = this.pseudoHasPatterns[i]; |  404   { | 
|  146  |  405     let stylesheet = event.target.sheet; | 
|  147       var prefixes = document.querySelectorAll(pattern.prefix); |  | 
|  148       for (var j = 0; j < prefixes.length; j++) |  | 
|  149       { |  | 
|  150         var matched = pattern.elementMatcher.match(prefixes[j]); |  | 
|  151         if (matched.length == 0) |  | 
|  152         { |  | 
|  153           continue; |  | 
|  154         } |  | 
|  155         // XXX make sure we don't have performance problems here |  | 
|  156         matched.forEach(function(e) |  | 
|  157         { |  | 
|  158           var subSelector = e.id; |  | 
|  159           if (!subSelector) |  | 
|  160           { |  | 
|  161             var findUniqueId = function() |  | 
|  162             { |  | 
|  163               var id = "elemHideEmulationHide-" + |  | 
|  164                   Math.floor(Math.random() * 10000); |  | 
|  165               if (!document.getElementById(id)) |  | 
|  166                 return id; |  | 
|  167               return findUniqueId(); |  | 
|  168             }; |  | 
|  169             subSelector = findUniqueId(); |  | 
|  170             e.id = subSelector; |  | 
|  171           } |  | 
|  172           var newSelector = pattern.prefix ? pattern.prefix + " > " : ""; |  | 
|  173           newSelector += "#" + subSelector; |  | 
|  174           newSelector += pattern.suffix ? " > " + pattern.suffix : ""; |  | 
|  175           selectors.push(newSelector); |  | 
|  176           filters.push(pattern.text); |  | 
|  177         }); |  | 
|  178       } |  | 
|  179     } |  | 
|  180   }, |  | 
|  181  |  | 
|  182   findPseudoClassHasElements: function(elements, filters) |  | 
|  183   { |  | 
|  184     for (var i = 0; i < this.pseudoHasPatterns.length; i++) |  | 
|  185     { |  | 
|  186       var pattern = this.pseudoHasPatterns[i]; |  | 
|  187  |  | 
|  188       var prefixes = document.querySelectorAll(pattern.prefix); |  | 
|  189       for (var j = 0; j < prefixes.length; j++) |  | 
|  190       { |  | 
|  191         var matched = pattern.elementMatcher.match(prefixes[j]); |  | 
|  192         if (matched.length == 0) |  | 
|  193         { |  | 
|  194           continue; |  | 
|  195         } |  | 
|  196  |  | 
|  197         matched.forEach(function(e) |  | 
|  198         { |  | 
|  199           var toHide = []; |  | 
|  200           var hidingPatterns = [] |  | 
|  201           if (pattern.suffix) |  | 
|  202           { |  | 
|  203             hidingPatterns.push(pattern.text); |  | 
|  204             console.log('pattern.suffix', pattern.suffix); |  | 
|  205             var sel = pattern.suffix; |  | 
|  206             // XXX we should have a more elegant way |  | 
|  207             // also startsWith isn't available in PhantomJS. |  | 
|  208             if (sel.substr(0, 1) == ">") |  | 
|  209             { |  | 
|  210               sel = sel.substr(1); |  | 
|  211             } |  | 
|  212             var subElements = e.querySelectorAll(sel); |  | 
|  213             for (var k = 0; k < subElements.length; k++) |  | 
|  214             { |  | 
|  215               elements.push(subElements[i]); |  | 
|  216               filters.push(pattern.text); |  | 
|  217             } |  | 
|  218           } |  | 
|  219           else |  | 
|  220           { |  | 
|  221             elements.push(e); |  | 
|  222             filters.push(pattern.text); |  | 
|  223           } |  | 
|  224         }); |  | 
|  225       } |  | 
|  226     } |  | 
|  227   }, |  | 
|  228  |  | 
|  229   addSelectors: function(stylesheets) |  | 
|  230   { |  | 
|  231     var selectors = []; |  | 
|  232     var filters = []; |  | 
|  233     for (var i = 0; i < stylesheets.length; i++) |  | 
|  234       this.findSelectors(stylesheets[i], selectors, filters); |  | 
|  235     this.addSelectorsFunc(selectors, filters); |  | 
|  236   }, |  | 
|  237  |  | 
|  238   hideElements: function() |  | 
|  239   { |  | 
|  240     var elements = []; |  | 
|  241     var filters = []; |  | 
|  242     this.findPseudoClassHasElements(elements, filters); |  | 
|  243     console.log("hideElements", elements.length); |  | 
|  244     this.hideElementsFunc(elements, filters); |  | 
|  245   }, |  | 
|  246  |  | 
|  247   onLoad: function(event) |  | 
|  248   { |  | 
|  249     var stylesheet = event.target.sheet; |  | 
|  250     if (stylesheet) |  406     if (stylesheet) | 
|  251       this.addSelectors([stylesheet]); |  407       this.addSelectors([stylesheet]); | 
|  252     this.hideElements(); |  408   }, | 
|  253   }, |  409  | 
|  254  |  410   apply() | 
|  255   apply: function() |  411   { | 
|  256   { |  412     this.getFiltersFunc(patterns => | 
|  257     this.getFiltersFunc(function(patterns) |  413     { | 
|  258     { |  414       let oldReportError = reportError; | 
|  259       this.propSelPatterns = []; |  415       reportError = error => this.window.console.error(error); | 
|  260       this.pseudoHasPatterns = []; |  416  | 
|  261       for (var i = 0; i < patterns.length; i++) |  417       this.patterns = []; | 
 |  418       for (let pattern of patterns) | 
|  262       { |  419       { | 
|  263         var pattern = patterns[i]; |  420         let selectors = parseSelector(pattern.selector); | 
|  264         var match = propertySelectorRegExp.exec(pattern.selector); |  421         if (selectors != null && selectors.length > 0) | 
|  265         if (match) |  422           this.patterns.push({selectors, text: pattern.text}); | 
|  266         { |  | 
|  267           var regexpMatcher; |  | 
|  268           var regexpString; |  | 
|  269           var propertyExpression = match[2]; |  | 
|  270           if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && |  | 
|  271               propertyExpression[propertyExpression.length - 1] == "/") |  | 
|  272             regexpString = propertyExpression.slice(1, -1) |  | 
|  273                 .replace("\\x7B ", "{").replace("\\x7D ", "}"); |  | 
|  274           else |  | 
|  275             regexpString = filterToRegExp(propertyExpression); |  | 
|  276           regexpMatcher = new RegExp(regexpString, "i"); |  | 
|  277           this.propSelPatterns.push({ |  | 
|  278             text: pattern.text, |  | 
|  279             regexp: regexpMatcher, |  | 
|  280             prefix: pattern.selector.substr(0, match.index), |  | 
|  281             suffix: pattern.selector.substr(match.index + match[0].length) |  | 
|  282           }); |  | 
|  283         } |  | 
|  284         else |  | 
|  285         { |  | 
|  286           var elementMatcher; |  | 
|  287           match = pseudoClassHasSelectorRegExp.exec(pattern.selector); |  | 
|  288           if (!match) |  | 
|  289             continue; |  | 
|  290  |  | 
|  291           var pseudoHasSelector = match[1]; |  | 
|  292           elementMatcher = new PseudoHasMatcher(pseudoHasSelector); |  | 
|  293           this.pseudoHasPatterns.push({ |  | 
|  294             text: pattern.text, |  | 
|  295             elementMatcher: elementMatcher, |  | 
|  296             prefix: pattern.selector.substr(0, match.index).trim(), |  | 
|  297             suffix: pattern.selector.substr(match.index + match[0].length).trim(
     ) |  | 
|  298           }); |  | 
|  299         } |  | 
|  300  |  | 
|  301       } |  423       } | 
|  302  |  424  | 
|  303       if (this.pseudoHasPatterns.length > 0 || this.propSelPatterns.length > 0) |  425       if (this.patterns.length > 0) | 
|  304       { |  426       { | 
|  305         var document = this.window.document; |  427         let {document} = this.window; | 
|  306         this.addSelectors(document.styleSheets); |  428         this.addSelectors(document.styleSheets); | 
|  307         this.hideElements(); |  | 
|  308         document.addEventListener("load", this.onLoad.bind(this), true); |  429         document.addEventListener("load", this.onLoad.bind(this), true); | 
|  309       } |  430       } | 
|  310     }.bind(this)); |  431       reportError = oldReportError; | 
 |  432     }); | 
|  311   } |  433   } | 
|  312 }; |  434 }; | 
| LEFT | RIGHT |