| OLD | NEW | 
|---|
| 1 /* | 1 /* | 
| 2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
| 3  * Copyright (C) 2006-2017 eyeo GmbH | 3  * Copyright (C) 2006-2017 eyeo GmbH | 
| 4  * | 4  * | 
| 5  * Adblock Plus is free software: you can redistribute it and/or modify | 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 | 6  * it under the terms of the GNU General Public License version 3 as | 
| 7  * published by the Free Software Foundation. | 7  * published by the Free Software Foundation. | 
| 8  * | 8  * | 
| 9  * Adblock Plus is distributed in the hope that it will be useful, | 9  * Adblock Plus is distributed in the hope that it will be useful, | 
| 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| 12  * GNU General Public License for more details. | 12  * GNU General Public License for more details. | 
| 13  * | 13  * | 
| 14  * You should have received a copy of the GNU General Public License | 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/>. | 15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
| 16  */ | 16  */ | 
| 17 | 17 | 
| 18 /* globals filterToRegExp */ | 18 /* globals filterToRegExp */ | 
| 19 | 19 | 
| 20 "use strict"; | 20 "use strict"; | 
| 21 | 21 | 
| 22 let propertySelectorRegExp = /\[-abp-properties=(["'])([^"']+)\1\]/; |  | 
| 23 |  | 
| 24 function splitSelector(selector) | 22 function splitSelector(selector) | 
| 25 { | 23 { | 
| 26   if (selector.indexOf(",") == -1) | 24   if (selector.indexOf(",") == -1) | 
| 27     return [selector]; | 25     return [selector]; | 
| 28 | 26 | 
| 29   let selectors = []; | 27   let selectors = []; | 
| 30   let start = 0; | 28   let start = 0; | 
| 31   let level = 0; | 29   let level = 0; | 
| 32   let sep = ""; | 30   let sep = ""; | 
| 33 | 31 | 
| (...skipping 18 matching lines...) Expand all  Loading... | 
| 52         selectors.push(selector.substring(start, i)); | 50         selectors.push(selector.substring(start, i)); | 
| 53         start = i + 1; | 51         start = i + 1; | 
| 54       } | 52       } | 
| 55     } | 53     } | 
| 56   } | 54   } | 
| 57 | 55 | 
| 58   selectors.push(selector.substring(start)); | 56   selectors.push(selector.substring(start)); | 
| 59   return selectors; | 57   return selectors; | 
| 60 } | 58 } | 
| 61 | 59 | 
|  | 60 // Return position of node from parent. | 
|  | 61 // 1 base index like for :nth-child() | 
|  | 62 function positionInParent(node) | 
|  | 63 { | 
|  | 64   let parentNode = node ? node.parentNode : null; | 
|  | 65   if (parentNode == null) | 
|  | 66     return 0; | 
|  | 67 | 
|  | 68   let {children} = parentNode; | 
|  | 69   if (!children) | 
|  | 70     return 0; | 
|  | 71   let i = 0; | 
|  | 72   for (i = 0; i < children.length; i++) | 
|  | 73     if (children[i] == node) | 
|  | 74       break; | 
|  | 75   return i + 1; | 
|  | 76 } | 
|  | 77 | 
|  | 78 function makeSelector(node, selector) | 
|  | 79 { | 
|  | 80   if (node && node.id && node.id != "") | 
|  | 81   { | 
|  | 82     let newSelector = "#" + node.id; | 
|  | 83     if (selector != "") | 
|  | 84       newSelector += " > "; | 
|  | 85     return newSelector + selector; | 
|  | 86   } | 
|  | 87   let idx = positionInParent(node); | 
|  | 88   if (idx > 0) | 
|  | 89   { | 
|  | 90     let newSelector = `${node.tagName}:nth-child(${idx}) `; | 
|  | 91     if (selector != "") | 
|  | 92       newSelector += "> "; | 
|  | 93     return makeSelector(node.parentNode, newSelector + selector); | 
|  | 94   } | 
|  | 95 | 
|  | 96   return selector; | 
|  | 97 } | 
|  | 98 | 
|  | 99 // return the regexString for the properties | 
|  | 100 function parsePropSelPattern(propertyExpression) | 
|  | 101 { | 
|  | 102   let regexpString; | 
|  | 103   if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 
|  | 104       propertyExpression[propertyExpression.length - 1] == "/") | 
|  | 105     regexpString = propertyExpression.slice(1, -1) | 
|  | 106     .replace("\\x7B ", "{").replace("\\x7D ", "}"); | 
|  | 107   else | 
|  | 108     regexpString = filterToRegExp(propertyExpression); | 
|  | 109   return regexpString; | 
|  | 110 } | 
|  | 111 | 
|  | 112 function parseSelector(selector) | 
|  | 113 { | 
|  | 114   if (selector.length == 0) | 
|  | 115     return []; | 
|  | 116 | 
|  | 117   let abpSelectorIndex = selector.indexOf(":-abp-"); | 
|  | 118   if (abpSelectorIndex == -1) | 
|  | 119     return [new PlainSelector(selector)]; | 
|  | 120 | 
|  | 121   let selectors = []; | 
|  | 122   if (abpSelectorIndex > 0) | 
|  | 123     selectors.push(new PlainSelector(selector.substr(0, abpSelectorIndex))); | 
|  | 124 | 
|  | 125   let suffixStart = abpSelectorIndex; | 
|  | 126 | 
|  | 127   if (selector.indexOf(":-abp-properties(", abpSelectorIndex) == | 
|  | 128       abpSelectorIndex) | 
|  | 129   { | 
|  | 130     let startIndex = abpSelectorIndex + 17; | 
|  | 131     let endquoteIndex = selector.indexOf(selector[startIndex], startIndex + 1); | 
|  | 132     if ((endquoteIndex == -1) || (selector[endquoteIndex + 1] != ")")) | 
|  | 133       return null; | 
|  | 134 | 
|  | 135     selectors.push(new PropsSelector( | 
|  | 136       selector.substr(startIndex + 1, endquoteIndex - startIndex - 1))); | 
|  | 137     suffixStart = endquoteIndex + 2; | 
|  | 138   } | 
|  | 139   else if (selector.indexOf(":-abp-has(", abpSelectorIndex) == | 
|  | 140       abpSelectorIndex) | 
|  | 141   { | 
|  | 142     let startIndex = abpSelectorIndex + 10; | 
|  | 143     let parens = 1; | 
|  | 144     let i; | 
|  | 145     for (i = startIndex; i < selector.length; i++) | 
|  | 146     { | 
|  | 147       if (selector[i] == "(") | 
|  | 148         parens++; | 
|  | 149       else if (selector[i] == ")") | 
|  | 150         parens--; | 
|  | 151 | 
|  | 152       if (parens == 0) | 
|  | 153         break; | 
|  | 154     } | 
|  | 155     if (parens != 0) | 
|  | 156       return null; | 
|  | 157     selectors.push(new HasSelector( | 
|  | 158       selector.substr(startIndex, i - startIndex))); | 
|  | 159     suffixStart = i + 1; | 
|  | 160   } | 
|  | 161 | 
|  | 162   let suffix = parseSelector(selector.substr(suffixStart)); | 
|  | 163   if (suffix) | 
|  | 164     selectors.push(...suffix); | 
|  | 165 | 
|  | 166   return selectors; | 
|  | 167 } | 
|  | 168 | 
|  | 169 function matchStyleProps(style, rule, pattern, selectors, filters) | 
|  | 170 { | 
|  | 171   if (pattern.regexp.test(style)) | 
|  | 172   { | 
|  | 173     let subSelectors = splitSelector(rule.selectorText); | 
|  | 174     for (let i = 0; i < subSelectors.length; i++) | 
|  | 175     { | 
|  | 176       let subSelector = subSelectors[i]; | 
|  | 177       selectors.push(pattern.prefix + subSelector + pattern.suffix); | 
|  | 178       filters.push(pattern.text); | 
|  | 179     } | 
|  | 180   } | 
|  | 181 } | 
|  | 182 | 
|  | 183 function findPropsSelectors(stylesheet, pattern, selectors, filters) | 
|  | 184 { | 
|  | 185   let rules = stylesheet.cssRules; | 
|  | 186   if (!rules) | 
|  | 187     return; | 
|  | 188 | 
|  | 189   for (let rule of rules) | 
|  | 190   { | 
|  | 191     if (rule.type != rule.STYLE_RULE) | 
|  | 192       continue; | 
|  | 193 | 
|  | 194     let style = stringifyStyle(rule.style); | 
|  | 195     matchStyleProps(style, rule, pattern, selectors, filters); | 
|  | 196   } | 
|  | 197 } | 
|  | 198 | 
|  | 199 function stringifyStyle(style) | 
|  | 200 { | 
|  | 201   let styles = []; | 
|  | 202   for (let i = 0; i < style.length; i++) | 
|  | 203   { | 
|  | 204     let property = style.item(i); | 
|  | 205     let value = style.getPropertyValue(property); | 
|  | 206     let priority = style.getPropertyPriority(property); | 
|  | 207     styles.push(property + ": " + value + (priority ? " !" + priority : "") + | 
|  | 208                 ";"); | 
|  | 209   } | 
|  | 210   styles.sort(); | 
|  | 211   return styles.join(" "); | 
|  | 212 } | 
|  | 213 | 
|  | 214 function* evaluate(chain, index, prefix, subtree, stylesheet) | 
|  | 215 { | 
|  | 216   if (index >= chain.length) | 
|  | 217   { | 
|  | 218     yield prefix; | 
|  | 219     return; | 
|  | 220   } | 
|  | 221   for (let [selector, element] of | 
|  | 222        chain[index].getSelectors(prefix, subtree, stylesheet)) | 
|  | 223     yield* evaluate(chain, index + 1, selector, element, stylesheet); | 
|  | 224 } | 
|  | 225 | 
|  | 226 /* | 
|  | 227  * getSelector() is a generator function returning a pair of selector | 
|  | 228  * string and subtree. | 
|  | 229  * getElements() is a generator function returning elements selected. | 
|  | 230  */ | 
|  | 231 function PlainSelector(selector) | 
|  | 232 { | 
|  | 233   this._selector = selector; | 
|  | 234 } | 
|  | 235 | 
|  | 236 PlainSelector.prototype = { | 
|  | 237   *getSelectors(prefix, subtree, stylesheet) | 
|  | 238   { | 
|  | 239     yield [prefix + this._selector, subtree]; | 
|  | 240   }, | 
|  | 241 | 
|  | 242   *getElements(prefix, subtree, stylesheet) | 
|  | 243   { | 
|  | 244     for (let selector of this.getSelectors(prefix, subtree, stylesheet)) | 
|  | 245       for (let element of subtree.querySelectorAll(selector[0])) | 
|  | 246         yield element; | 
|  | 247   } | 
|  | 248 }; | 
|  | 249 | 
|  | 250 function HasSelector(selector) | 
|  | 251 { | 
|  | 252   this._innerSelectors = parseSelector(selector); | 
|  | 253 } | 
|  | 254 | 
|  | 255 HasSelector.prototype = { | 
|  | 256   *getSelectors(prefix, subtree, stylesheet) | 
|  | 257   { | 
|  | 258     for (let element of this.getElements(prefix, subtree, stylesheet)) | 
|  | 259       yield [prefix + makeSelector(element, ""), subtree]; | 
|  | 260   }, | 
|  | 261 | 
|  | 262   *getElements(prefix, subtree, stylesheet) | 
|  | 263   { | 
|  | 264     let elements = subtree.querySelectorAll(prefix ? prefix : "*"); | 
|  | 265     for (let element of elements) | 
|  | 266     { | 
|  | 267       let newPrefix = makeSelector(element, ""); | 
|  | 268       let iter = evaluate(this._innerSelectors, 0, "", element, stylesheet); | 
|  | 269       for (let selector of iter) | 
|  | 270         // we insert a space between the two. It becomes a no-op if selector | 
|  | 271         // doesn't have a combinator | 
|  | 272         if (subtree.querySelector(newPrefix + " " + selector)) | 
|  | 273           yield element; | 
|  | 274     } | 
|  | 275   } | 
|  | 276 }; | 
|  | 277 | 
|  | 278 function PropsSelector(selector) | 
|  | 279 { | 
|  | 280   this._regexp = new RegExp(parsePropSelPattern(selector), "i"); | 
|  | 281 } | 
|  | 282 | 
|  | 283 PropsSelector.prototype = { | 
|  | 284   *getSelectors(prefix, subtree, stylesheet) | 
|  | 285   { | 
|  | 286     let selectors = []; | 
|  | 287     let filters = []; | 
|  | 288     let selPattern = { | 
|  | 289       prefix, | 
|  | 290       suffix: "", | 
|  | 291       regexp: this._regexp | 
|  | 292     }; | 
|  | 293 | 
|  | 294     findPropsSelectors(stylesheet, selPattern, selectors, filters); | 
|  | 295     for (let selector of selectors) | 
|  | 296       yield [selector, subtree]; | 
|  | 297   }, | 
|  | 298 | 
|  | 299   *getElements(prefix, subtree, stylesheet) | 
|  | 300   { | 
|  | 301     for (let [selector, element] of | 
|  | 302          this.getSelectors(prefix, subtree, stylesheet)) | 
|  | 303       for (let subElement of element.querySelectorAll(selector)) | 
|  | 304         yield subElement; | 
|  | 305   } | 
|  | 306 }; | 
|  | 307 | 
| 62 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc) | 308 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc) | 
| 63 { | 309 { | 
| 64   this.window = window; | 310   this.window = window; | 
| 65   this.getFiltersFunc = getFiltersFunc; | 311   this.getFiltersFunc = getFiltersFunc; | 
| 66   this.addSelectorsFunc = addSelectorsFunc; | 312   this.addSelectorsFunc = addSelectorsFunc; | 
| 67 } | 313 } | 
| 68 | 314 | 
| 69 ElemHideEmulation.prototype = { | 315 ElemHideEmulation.prototype = { | 
| 70   stringifyStyle(style) |  | 
| 71   { |  | 
| 72     let styles = []; |  | 
| 73     for (let i = 0; i < style.length; i++) |  | 
| 74     { |  | 
| 75       let property = style.item(i); |  | 
| 76       let value = style.getPropertyValue(property); |  | 
| 77       let priority = style.getPropertyPriority(property); |  | 
| 78       styles.push(property + ": " + value + (priority ? " !" + priority : "") + |  | 
| 79                   ";"); |  | 
| 80     } |  | 
| 81     styles.sort(); |  | 
| 82     return styles.join(" "); |  | 
| 83   }, |  | 
| 84 | 316 | 
| 85   isSameOrigin(stylesheet) | 317   isSameOrigin(stylesheet) | 
| 86   { | 318   { | 
| 87     try | 319     try | 
| 88     { | 320     { | 
| 89       return new URL(stylesheet.href).origin == this.window.location.origin; | 321       return new URL(stylesheet.href).origin == this.window.location.origin; | 
| 90     } | 322     } | 
| 91     catch (e) | 323     catch (e) | 
| 92     { | 324     { | 
| 93       // Invalid URL, assume that it is first-party. | 325       // Invalid URL, assume that it is first-party. | 
| 94       return true; | 326       return true; | 
| 95     } | 327     } | 
| 96   }, | 328   }, | 
| 97 | 329 | 
| 98   findSelectors(stylesheet, selectors, filters) | 330   addSelectors(stylesheet) | 
| 99   { | 331   { | 
|  | 332     let selectors = []; | 
|  | 333     let filters = []; | 
|  | 334 | 
| 100     // Explicitly ignore third-party stylesheets to ensure consistent behavior | 335     // Explicitly ignore third-party stylesheets to ensure consistent behavior | 
| 101     // between Firefox and Chrome. | 336     // between Firefox and Chrome. | 
| 102     if (!this.isSameOrigin(stylesheet)) | 337     if (!this.isSameOrigin(stylesheet)) | 
| 103       return; | 338       return; | 
| 104 | 339 | 
| 105     let rules = stylesheet.cssRules; | 340     for (let patterns of this.selPatterns) | 
| 106     if (!rules) | 341       selectors.push(...evaluate(patterns, 0, "", document, stylesheet)); | 
| 107       return; | 342 | 
| 108 |  | 
| 109     for (let rule of rules) |  | 
| 110     { |  | 
| 111       if (rule.type != rule.STYLE_RULE) |  | 
| 112         continue; |  | 
| 113 |  | 
| 114       let style = this.stringifyStyle(rule.style); |  | 
| 115       for (let pattern of this.patterns) |  | 
| 116       { |  | 
| 117         if (pattern.regexp.test(style)) |  | 
| 118         { |  | 
| 119           let subSelectors = splitSelector(rule.selectorText); |  | 
| 120           for (let subSelector of subSelectors) |  | 
| 121           { |  | 
| 122             selectors.push(pattern.prefix + subSelector + pattern.suffix); |  | 
| 123             filters.push(pattern.text); |  | 
| 124           } |  | 
| 125         } |  | 
| 126       } |  | 
| 127     } |  | 
| 128   }, |  | 
| 129 |  | 
| 130   addSelectors(stylesheets) |  | 
| 131   { |  | 
| 132     let selectors = []; |  | 
| 133     let filters = []; |  | 
| 134     for (let stylesheet of stylesheets) |  | 
| 135       this.findSelectors(stylesheet, selectors, filters); |  | 
| 136     this.addSelectorsFunc(selectors, filters); | 343     this.addSelectorsFunc(selectors, filters); | 
| 137   }, | 344   }, | 
| 138 | 345 | 
| 139   onLoad(event) | 346   onLoad(event) | 
| 140   { | 347   { | 
| 141     let stylesheet = event.target.sheet; | 348     let stylesheet = event.target.sheet; | 
| 142     if (stylesheet) | 349     if (stylesheet) | 
| 143       this.addSelectors([stylesheet]); | 350       this.addSelectors(stylesheet); | 
| 144   }, | 351   }, | 
| 145 | 352 | 
| 146   apply() | 353   apply() | 
| 147   { | 354   { | 
| 148     this.getFiltersFunc(patterns => | 355     this.getFiltersFunc(patterns => | 
| 149     { | 356     { | 
| 150       this.patterns = []; | 357       this.selPatterns = []; | 
|  | 358 | 
| 151       for (let pattern of patterns) | 359       for (let pattern of patterns) | 
| 152       { | 360       { | 
| 153         let match = propertySelectorRegExp.exec(pattern.selector); | 361         let selectors = parseSelector(pattern.selector); | 
| 154         if (!match) | 362         if (selectors != null && selectors.length > 0) | 
| 155           continue; | 363           this.selPatterns.push(selectors); | 
| 156 |  | 
| 157         let propertyExpression = match[2]; |  | 
| 158         let regexpString; |  | 
| 159         if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && |  | 
| 160             propertyExpression[propertyExpression.length - 1] == "/") |  | 
| 161         { |  | 
| 162           regexpString = propertyExpression.slice(1, -1) |  | 
| 163               .replace("\\x7B ", "{").replace("\\x7D ", "}"); |  | 
| 164         } |  | 
| 165         else |  | 
| 166           regexpString = filterToRegExp(propertyExpression); |  | 
| 167 |  | 
| 168         this.patterns.push({ |  | 
| 169           text: pattern.text, |  | 
| 170           regexp: new RegExp(regexpString, "i"), |  | 
| 171           prefix: pattern.selector.substr(0, match.index), |  | 
| 172           suffix: pattern.selector.substr(match.index + match[0].length) |  | 
| 173         }); |  | 
| 174       } | 364       } | 
| 175 | 365 | 
| 176       if (this.patterns.length > 0) | 366       if (this.selPatterns.length > 0) | 
| 177       { | 367       { | 
| 178         let {document} = this.window; | 368         let {document} = this.window; | 
| 179         this.addSelectors(document.styleSheets); | 369         for (let stylesheet of document.styleSheets) | 
|  | 370           this.addSelectors(stylesheet); | 
| 180         document.addEventListener("load", this.onLoad.bind(this), true); | 371         document.addEventListener("load", this.onLoad.bind(this), true); | 
| 181       } | 372       } | 
| 182     }); | 373     }); | 
| 183   } | 374   } | 
| 184 }; | 375 }; | 
| OLD | NEW | 
|---|