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