| LEFT | RIGHT | 
|---|
| 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 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 
|  | 23 | 
|  | 24 let reportError = () => {}; | 
|  | 25 | 
| 22 function splitSelector(selector) | 26 function splitSelector(selector) | 
| 23 { | 27 { | 
| 24   if (selector.indexOf(",") == -1) | 28   if (selector.indexOf(",") == -1) | 
| 25     return [selector]; | 29     return [selector]; | 
| 26 | 30 | 
| 27   let selectors = []; | 31   let selectors = []; | 
| 28   let start = 0; | 32   let start = 0; | 
| 29   let level = 0; | 33   let level = 0; | 
| 30   let sep = ""; | 34   let sep = ""; | 
| 31 | 35 | 
| (...skipping 19 matching lines...) Expand all  Loading... | 
| 51         start = i + 1; | 55         start = i + 1; | 
| 52       } | 56       } | 
| 53     } | 57     } | 
| 54   } | 58   } | 
| 55 | 59 | 
| 56   selectors.push(selector.substring(start)); | 60   selectors.push(selector.substring(start)); | 
| 57   return selectors; | 61   return selectors; | 
| 58 } | 62 } | 
| 59 | 63 | 
| 60 /** Return position of node from parent. | 64 /** Return position of node from parent. | 
| 61  * @param {Node} node - the node to find the position of. | 65  * @param {Node} node the node to find the position of. | 
| 62  * @return {number} 1 base index like for :nth-child(), or 0 on error. | 66  * @return {number} One-based index like for :nth-child(), or 0 on error. | 
| 63  */ | 67  */ | 
| 64 function positionInParent(node) | 68 function positionInParent(node) | 
| 65 { | 69 { | 
| 66   if (!node) |  | 
| 67     return 0; |  | 
| 68   let {children} = node.parentNode; | 70   let {children} = node.parentNode; | 
| 69   for (let i = 0; i < children.length; i++) | 71   for (let i = 0; i < children.length; i++) | 
| 70     if (children[i] == node) | 72     if (children[i] == node) | 
| 71       return i + 1; | 73       return i + 1; | 
| 72   return 0; | 74   return 0; | 
| 73 } | 75 } | 
| 74 | 76 | 
| 75 function makeSelector(node, selector) | 77 function makeSelector(node, selector) | 
| 76 { | 78 { | 
| 77   if (!node.parentElement) | 79   if (!node.parentElement) | 
| 78   { | 80   { | 
| 79     let newSelector = ":root"; | 81     let newSelector = ":root"; | 
| 80     if (selector) | 82     if (selector) | 
| 81       newSelector += " > "; | 83       newSelector += " > " + selector; | 
| 82     return newSelector + selector; | 84     return newSelector; | 
| 83   } | 85   } | 
| 84   let idx = positionInParent(node); | 86   let idx = positionInParent(node); | 
| 85   if (idx > 0) | 87   if (idx > 0) | 
| 86   { | 88   { | 
| 87     let newSelector = `${node.tagName}:nth-child(${idx})`; | 89     let newSelector = `${node.tagName}:nth-child(${idx})`; | 
| 88     if (selector) | 90     if (selector) | 
| 89       newSelector += " > "; | 91       newSelector += " > " + selector; | 
| 90     return makeSelector(node.parentElement, newSelector + selector); | 92     return makeSelector(node.parentElement, newSelector); | 
| 91   } | 93   } | 
| 92 | 94 | 
| 93   return selector; | 95   return selector; | 
| 94 } | 96 } | 
| 95 | 97 | 
| 96 const abpSelectorRegexp = /:-abp-(properties|has|[A-Za-z\d-]*)\(/i; | 98 function parseSelectorContent(content, startIndex) | 
| 97 |  | 
| 98 function parseSelectorContent(content, quoted = false) |  | 
| 99 { | 99 { | 
| 100   let parens = 1; | 100   let parens = 1; | 
| 101   let i = 0; |  | 
| 102   let quote = null; | 101   let quote = null; | 
| 103   let originalLength = content.length; | 102   let i = startIndex; | 
| 104   if (quoted) | 103   for (; i < content.length; i++) | 
| 105     content = content.trim(); |  | 
| 106   while (i < content.length) |  | 
| 107   { | 104   { | 
| 108     let c = content[i]; | 105     let c = content[i]; | 
| 109     if (quoted && i == 0) |  | 
| 110     { |  | 
| 111       if (c != "'" && c != '"') |  | 
| 112         return null; |  | 
| 113     } |  | 
| 114     if (c == "\\") | 106     if (c == "\\") | 
|  | 107     { | 
|  | 108       // Ignore escaped characters | 
| 115       i++; | 109       i++; | 
|  | 110     } | 
| 116     else if (quote) | 111     else if (quote) | 
| 117     { | 112     { | 
| 118       if (c == quote) | 113       if (c == quote) | 
| 119         quote = null; | 114         quote = null; | 
| 120     } | 115     } | 
| 121     else if (c == "'" || c == '"') | 116     else if (c == "'" || c == '"') | 
| 122     { |  | 
| 123       quote = c; | 117       quote = c; | 
| 124     } |  | 
| 125     else if (c == "(") | 118     else if (c == "(") | 
| 126       parens++; | 119       parens++; | 
| 127     else if (c == ")") | 120     else if (c == ")") | 
| 128     { | 121     { | 
| 129       parens--; | 122       parens--; | 
| 130       if (parens == 0) | 123       if (parens == 0) | 
| 131         break; | 124         break; | 
| 132     } | 125     } | 
| 133     i++; | 126   } | 
| 134   } | 127 | 
| 135   if (parens > 0) | 128   if (parens > 0) | 
| 136     return null; | 129     return null; | 
| 137   if (quoted) | 130   return {text: content.substring(startIndex, i), end: i}; | 
| 138   { | 131 } | 
| 139     let end = content.substr(0, i).lastIndexOf(content[0]); | 132 | 
| 140     return {text: content.substr(1, end - 1), | 133 /** Parse the selector | 
| 141       end: i + (originalLength - content.length)}; | 134  * @param {string} selector the selector to parse | 
| 142   } | 135  * @return {Object} selectors is an array of objects, | 
| 143   return {text: content.substr(0, i), end: i}; | 136  * or null in case of errors. hide is true if we'll hide | 
| 144 } | 137  * elements instead of styles.. | 
| 145 | 138  */ | 
| 146 function parseSelector(selector, level = 0) | 139 function parseSelector(selector) | 
| 147 { | 140 { | 
| 148   if (selector.length == 0) | 141   if (selector.length == 0) | 
| 149     return []; | 142     return []; | 
| 150 | 143 | 
| 151   let match = abpSelectorRegexp.exec(selector); | 144   let match = abpSelectorRegexp.exec(selector); | 
| 152   if (!match) | 145   if (!match) | 
| 153     return [new PlainSelector(selector)]; | 146     return [new PlainSelector(selector)]; | 
| 154 | 147 | 
| 155   let selectors = []; | 148   let selectors = []; | 
| 156   let suffixStart = match.index; | 149   if (match.index > 0) | 
| 157   if (suffixStart > 0) | 150     selectors.push(new PlainSelector(selector.substr(0, match.index))); | 
| 158     selectors.push(new PlainSelector(selector.substr(0, suffixStart))); |  | 
| 159 | 151 | 
| 160   let startIndex = match.index + match[0].length; | 152   let startIndex = match.index + match[0].length; | 
| 161   let content = null; | 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   } | 
| 162   if (match[1] == "properties") | 161   if (match[1] == "properties") | 
| 163   { |  | 
| 164     content = parseSelectorContent(selector.substr(startIndex), true); |  | 
| 165     if (content == null) |  | 
| 166     { |  | 
| 167       console.error(new SyntaxError("Failed to parse AdBlock Plus " + |  | 
| 168                                     `selector ${selector}, invalid ` + |  | 
| 169                                     "properties string.")); |  | 
| 170       return null; |  | 
| 171     } |  | 
| 172 |  | 
| 173     selectors.push(new PropsSelector(content.text)); | 162     selectors.push(new PropsSelector(content.text)); | 
| 174   } |  | 
| 175   else if (match[1] == "has") | 163   else if (match[1] == "has") | 
| 176   { | 164   { | 
| 177     if (level > 0) |  | 
| 178     { |  | 
| 179       console.error(new SyntaxError("Failed to parse AdBlock Plus " + |  | 
| 180                                     `selector ${selector}, invalid ` + |  | 
| 181                                     "nested :-abp-has().")); |  | 
| 182       return null; |  | 
| 183     } |  | 
| 184 |  | 
| 185     content = parseSelectorContent(selector.substr(startIndex)); |  | 
| 186     if (content == null) |  | 
| 187     { |  | 
| 188       console.error(new SyntaxError("Failed parsing AdBlock Plus " + |  | 
| 189                                     `selector ${selector}, didn't ` + |  | 
| 190                                     "find closing parenthesis.")); |  | 
| 191       return null; |  | 
| 192     } |  | 
| 193 |  | 
| 194     let hasSelector = new HasSelector(content.text); | 165     let hasSelector = new HasSelector(content.text); | 
| 195     if (!hasSelector.valid()) | 166     if (!hasSelector.valid()) | 
| 196       return null; | 167       return null; | 
| 197     selectors.push(hasSelector); | 168     selectors.push(hasSelector); | 
| 198   } | 169   } | 
| 199   else | 170   else | 
| 200   { | 171   { | 
| 201     // this is an error, can't parse selector. | 172     // this is an error, can't parse selector. | 
| 202     console.error(new SyntaxError("Failed parsing AdBlock Plus " + | 173     reportError(new SyntaxError("Failed to parse Adblock Plus " + | 
| 203                                   `selector ${selector}, invalid ` + | 174                                 `selector ${selector}, invalid ` + | 
| 204                                   `pseudo-class -abp-${match[1]}.`)); | 175                                 `pseudo-class :-abp-${match[1]}().`)); | 
| 205     return null; | 176     return null; | 
| 206   } | 177   } | 
| 207 | 178 | 
| 208   suffixStart = startIndex + content.end + 1; | 179   let suffix = parseSelector(selector.substr(content.end + 1)); | 
| 209 |  | 
| 210   let suffix = parseSelector(selector.substr(suffixStart), level); |  | 
| 211   if (suffix == null) | 180   if (suffix == null) | 
| 212     return null; | 181     return null; | 
| 213 | 182 | 
| 214   selectors.push(...suffix); | 183   selectors.push(...suffix); | 
| 215 | 184 | 
| 216   return selectors; | 185   return selectors; | 
| 217 } | 186 } | 
| 218 | 187 | 
| 219 function *findPropsSelectors(styles, prefix, regexp) | 188 /** Stringified style objects | 
| 220 { | 189  * @typedef {Object} StringifiedStyle | 
| 221   for (let style of styles) | 190  * @property {string} style CSS style represented by a string. | 
| 222     if (regexp.test(style.style)) | 191  * @property {string[]} subSelectors selectors the CSS properties apply to. | 
| 223       for (let subSelector of style.subSelectors) | 192  */ | 
| 224         yield prefix + subSelector; | 193 | 
| 225 } | 194 /** | 
| 226 | 195  * Produce a string representation of the stylesheet entry. | 
| 227 function stringifyStyle(style) | 196  * @param {CSSStyleRule} rule the CSS style rule. | 
|  | 197  * @return {StringifiedStyle} the stringified style. | 
|  | 198  */ | 
|  | 199 function stringifyStyle(rule) | 
| 228 { | 200 { | 
| 229   let styles = []; | 201   let styles = []; | 
| 230   for (let i = 0; i < style.length; i++) | 202   for (let i = 0; i < rule.style.length; i++) | 
| 231   { | 203   { | 
| 232     let property = style.item(i); | 204     let property = rule.style.item(i); | 
| 233     let value = style.getPropertyValue(property); | 205     let value = rule.style.getPropertyValue(property); | 
| 234     let priority = style.getPropertyPriority(property); | 206     let priority = rule.style.getPropertyPriority(property); | 
| 235     styles.push(property + ": " + value + (priority ? " !" + priority : "") + | 207     styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`); | 
| 236                 ";"); |  | 
| 237   } | 208   } | 
| 238   styles.sort(); | 209   styles.sort(); | 
| 239   return styles.join(" "); | 210   return { | 
|  | 211     style: styles.join(" "), | 
|  | 212     subSelectors: splitSelector(rule.selectorText) | 
|  | 213   }; | 
| 240 } | 214 } | 
| 241 | 215 | 
| 242 function* evaluate(chain, index, prefix, subtree, styles) | 216 function* evaluate(chain, index, prefix, subtree, styles) | 
| 243 { | 217 { | 
| 244   if (index >= chain.length) | 218   if (index >= chain.length) | 
| 245   { | 219   { | 
| 246     yield prefix; | 220     yield prefix; | 
| 247     return; | 221     return; | 
| 248   } | 222   } | 
| 249   for (let [selector, element] of | 223   for (let [selector, element] of | 
| 250        chain[index].getSelectors(prefix, subtree, styles)) | 224        chain[index].getSelectors(prefix, subtree, styles)) | 
| 251     yield* evaluate(chain, index + 1, selector, element, styles); | 225     yield* evaluate(chain, index + 1, selector, element, styles); | 
| 252 } | 226 } | 
| 253 | 227 | 
| 254 function PlainSelector(selector) | 228 function PlainSelector(selector) | 
| 255 { | 229 { | 
| 256   this._selector = selector; | 230   this._selector = selector; | 
| 257 } | 231 } | 
| 258 | 232 | 
| 259 PlainSelector.prototype = { | 233 PlainSelector.prototype = { | 
| 260   /** | 234   /** | 
| 261    * Generator function returning a pair of selector | 235    * Generator function returning a pair of selector | 
| 262    * string and subtree. | 236    * string and subtree. | 
| 263    * @param {String} prefix - the prefix for the selector. | 237    * @param {string} prefix the prefix for the selector. | 
| 264    * @param {Node} subtree - the subtree we work on. | 238    * @param {Node} subtree the subtree we work on. | 
| 265    * @param {Array} styles - the stringified stylesheets. | 239    * @param {StringifiedStyle[]} styles the stringified style objects. | 
| 266    */ | 240    */ | 
| 267   *getSelectors(prefix, subtree, styles) | 241   *getSelectors(prefix, subtree, styles) | 
| 268   { | 242   { | 
| 269     yield [prefix + this._selector, subtree]; | 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]; | 
| 270   }, | 266   }, | 
| 271 | 267 | 
| 272   /** | 268   /** | 
| 273    * Generator function returning selected elements. | 269    * Generator function returning selected elements. | 
| 274    * @param {String} prefix - the prefix for the selector. | 270    * @param {string} prefix the prefix for the selector. | 
| 275    * @param {Node} subtree - the subtree we work on. | 271    * @param {Node} subtree the subtree we work on. | 
| 276    * @param {Array} styles - the stringified stylesheets. | 272    * @param {StringifiedStyle[]} styles the stringified style objects. | 
| 277    */ | 273    */ | 
| 278   *getElements(prefix, subtree, styles) | 274   *getElements(prefix, subtree, styles) | 
| 279   { | 275   { | 
| 280     for (let [selector] of this.getSelectors(prefix, subtree, styles)) | 276     let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 
| 281       for (let element of subtree.querySelectorAll(selector)) |  | 
| 282         yield element; |  | 
| 283   } |  | 
| 284 }; |  | 
| 285 |  | 
| 286 const prefixEndRegexp = /[\s>+~]$/; |  | 
| 287 |  | 
| 288 function HasSelector(selector, level = 0) |  | 
| 289 { |  | 
| 290   this._innerSelectors = parseSelector(selector, level + 1); |  | 
| 291 } |  | 
| 292 |  | 
| 293 HasSelector.prototype = { |  | 
| 294   valid() |  | 
| 295   { |  | 
| 296     return this._innerSelectors != null; |  | 
| 297   }, |  | 
| 298 |  | 
| 299   *getSelectors(prefix, subtree, styles) |  | 
| 300   { |  | 
| 301     for (let element of this.getElements(prefix, subtree, styles)) |  | 
| 302       yield [makeSelector(element, ""), element]; |  | 
| 303   }, |  | 
| 304 |  | 
| 305   *getElements(prefix, subtree, styles) |  | 
| 306   { |  | 
| 307     let actualPrefix = (!prefix || prefixEndRegexp.test(prefix)) ? |  | 
| 308         prefix + "*" : prefix; | 277         prefix + "*" : prefix; | 
| 309     let elements = subtree.querySelectorAll(actualPrefix); | 278     let elements = subtree.querySelectorAll(actualPrefix); | 
| 310     for (let element of elements) | 279     for (let element of elements) | 
| 311     { | 280     { | 
| 312       let newPrefix = makeSelector(element, ""); | 281       let newPrefix = makeSelector(element, ""); | 
| 313       let iter = evaluate(this._innerSelectors, 0, "", element, styles); | 282       let iter = evaluate(this._innerSelectors, 0, newPrefix + " ", | 
|  | 283                           element, styles); | 
| 314       for (let selector of iter) | 284       for (let selector of iter) | 
| 315         // we insert a space between the two. It becomes a no-op if selector | 285         // we insert a space between the two. It becomes a no-op if selector | 
| 316         // doesn't have a combinator | 286         // doesn't have a combinator | 
| 317         if (subtree.querySelector(newPrefix + " " + selector)) | 287         if (subtree.querySelector(selector)) | 
| 318           yield element; | 288           yield element; | 
| 319     } | 289     } | 
| 320   } | 290   } | 
| 321 }; | 291 }; | 
| 322 | 292 | 
| 323 function PropsSelector(propertyExpression) | 293 function PropsSelector(propertyExpression) | 
| 324 { | 294 { | 
| 325   let regexpString; | 295   let regexpString; | 
| 326   if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 296   if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 
| 327       propertyExpression[propertyExpression.length - 1] == "/") | 297       propertyExpression[propertyExpression.length - 1] == "/") | 
| 328   { | 298   { | 
| 329     regexpString = propertyExpression.slice(1, -1) | 299     regexpString = propertyExpression.slice(1, -1) | 
| 330       .replace("\\x7B ", "{").replace("\\x7D ", "}"); | 300       .replace("\\x7B ", "{").replace("\\x7D ", "}"); | 
| 331   } | 301   } | 
| 332   else | 302   else | 
| 333     regexpString = filterToRegExp(propertyExpression); | 303     regexpString = filterToRegExp(propertyExpression); | 
| 334 | 304 | 
| 335   this._regexp = new RegExp(regexpString, "i"); | 305   this._regexp = new RegExp(regexpString, "i"); | 
| 336 } | 306 } | 
| 337 | 307 | 
| 338 PropsSelector.prototype = { | 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 | 
| 339   *getSelectors(prefix, subtree, styles) | 317   *getSelectors(prefix, subtree, styles) | 
| 340   { | 318   { | 
| 341     for (let selector of findPropsSelectors(styles, prefix, this._regexp)) | 319     for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 
| 342       yield [selector, subtree]; | 320       yield [selector, subtree]; | 
| 343   }, |  | 
| 344 |  | 
| 345   *getElements(prefix, subtree, styles) |  | 
| 346   { |  | 
| 347     for (let [selector] of this.getSelectors(prefix, subtree, styles)) |  | 
| 348       for (let subElement of subtree.querySelectorAll(selector)) |  | 
| 349         yield subElement; |  | 
| 350   } | 321   } | 
| 351 }; | 322 }; | 
| 352 | 323 | 
| 353 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc) | 324 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, | 
|  | 325                            hideElemsFunc) | 
| 354 { | 326 { | 
| 355   this.window = window; | 327   this.window = window; | 
| 356   this.getFiltersFunc = getFiltersFunc; | 328   this.getFiltersFunc = getFiltersFunc; | 
| 357   this.addSelectorsFunc = addSelectorsFunc; | 329   this.addSelectorsFunc = addSelectorsFunc; | 
|  | 330   this.hideElemsFunc = hideElemsFunc; | 
| 358 } | 331 } | 
| 359 | 332 | 
| 360 ElemHideEmulation.prototype = { | 333 ElemHideEmulation.prototype = { | 
| 361   isSameOrigin(stylesheet) | 334   isSameOrigin(stylesheet) | 
| 362   { | 335   { | 
| 363     try | 336     try | 
| 364     { | 337     { | 
| 365       return new URL(stylesheet.href).origin == this.window.location.origin; | 338       return new URL(stylesheet.href).origin == this.window.location.origin; | 
| 366     } | 339     } | 
| 367     catch (e) | 340     catch (e) | 
| 368     { | 341     { | 
| 369       // Invalid URL, assume that it is first-party. | 342       // Invalid URL, assume that it is first-party. | 
| 370       return true; | 343       return true; | 
| 371     } | 344     } | 
| 372   }, | 345   }, | 
| 373 | 346 | 
| 374   addSelectors(stylesheets) | 347   addSelectors(stylesheets) | 
| 375   { | 348   { | 
| 376     let selectors = []; | 349     let selectors = []; | 
| 377     let filters = []; | 350     let selectorFilters = []; | 
|  | 351 | 
|  | 352     let elements = []; | 
|  | 353     let elementFilters = []; | 
| 378 | 354 | 
| 379     let cssStyles = []; | 355     let cssStyles = []; | 
| 380 | 356 | 
| 381     for (let stylesheet of stylesheets) | 357     for (let stylesheet of stylesheets) | 
| 382     { | 358     { | 
| 383       // Explicitly ignore third-party stylesheets to ensure consistent behavior | 359       // Explicitly ignore third-party stylesheets to ensure consistent behavior | 
| 384       // between Firefox and Chrome. | 360       // between Firefox and Chrome. | 
| 385       if (!this.isSameOrigin(stylesheet)) | 361       if (!this.isSameOrigin(stylesheet)) | 
| 386         continue; | 362         continue; | 
| 387 | 363 | 
| 388       let rules = stylesheet.cssRules; | 364       let rules = stylesheet.cssRules; | 
| 389       if (!rules) | 365       if (!rules) | 
| 390         continue; | 366         continue; | 
| 391 | 367 | 
| 392       for (let rule of rules) | 368       for (let rule of rules) | 
| 393       { | 369       { | 
| 394         if (rule.type != rule.STYLE_RULE) | 370         if (rule.type != rule.STYLE_RULE) | 
| 395           continue; | 371           continue; | 
| 396 | 372 | 
| 397         let style = stringifyStyle(rule.style); | 373         cssStyles.push(stringifyStyle(rule)); | 
| 398         let subSelectors = splitSelector(rule.selectorText); |  | 
| 399         cssStyles.push({style, subSelectors}); |  | 
| 400       } | 374       } | 
| 401     } | 375     } | 
| 402 | 376 | 
| 403     for (let patterns of this.selPatterns) | 377     let {document} = this.window; | 
| 404       for (let selector of evaluate(patterns.selectors, | 378     for (let pattern of this.patterns) | 
|  | 379     { | 
|  | 380       for (let selector of evaluate(pattern.selectors, | 
| 405                                     0, "", document, cssStyles)) | 381                                     0, "", document, cssStyles)) | 
| 406       { | 382       { | 
| 407         selectors.push(selector); | 383         if (!pattern.selectors.some(s => s.requiresHiding)) | 
| 408         filters.push(patterns.text); | 384         { | 
|  | 385           selectors.push(selector); | 
|  | 386           selectorFilters.push(pattern.text); | 
|  | 387         } | 
|  | 388         else | 
|  | 389         { | 
|  | 390           for (let element of document.querySelectorAll(selector)) | 
|  | 391           { | 
|  | 392             elements.push(element); | 
|  | 393             elementFilters.push(pattern.text); | 
|  | 394           } | 
|  | 395         } | 
| 409       } | 396       } | 
| 410 | 397     } | 
| 411     this.addSelectorsFunc(selectors, filters); | 398 | 
|  | 399     this.addSelectorsFunc(selectors, selectorFilters); | 
|  | 400     this.hideElemsFunc(elements, elementFilters); | 
| 412   }, | 401   }, | 
| 413 | 402 | 
| 414   onLoad(event) | 403   onLoad(event) | 
| 415   { | 404   { | 
| 416     let stylesheet = event.target.sheet; | 405     let stylesheet = event.target.sheet; | 
| 417     if (stylesheet) | 406     if (stylesheet) | 
| 418       this.addSelectors([stylesheet]); | 407       this.addSelectors([stylesheet]); | 
| 419   }, | 408   }, | 
| 420 | 409 | 
| 421   apply() | 410   apply() | 
| 422   { | 411   { | 
| 423     this.getFiltersFunc(patterns => | 412     this.getFiltersFunc(patterns => | 
| 424     { | 413     { | 
| 425       this.selPatterns = []; | 414       let oldReportError = reportError; | 
| 426 | 415       reportError = error => this.window.console.error(error); | 
|  | 416 | 
|  | 417       this.patterns = []; | 
| 427       for (let pattern of patterns) | 418       for (let pattern of patterns) | 
| 428       { | 419       { | 
| 429         let selectors = parseSelector(pattern.selector); | 420         let selectors = parseSelector(pattern.selector); | 
| 430         if (selectors != null && selectors.length > 0) | 421         if (selectors != null && selectors.length > 0) | 
| 431           this.selPatterns.push({selectors, text: pattern.text}); | 422           this.patterns.push({selectors, text: pattern.text}); | 
| 432       } | 423       } | 
| 433 | 424 | 
| 434       if (this.selPatterns.length > 0) | 425       if (this.patterns.length > 0) | 
| 435       { | 426       { | 
| 436         let {document} = this.window; | 427         let {document} = this.window; | 
| 437         this.addSelectors(document.styleSheets); | 428         this.addSelectors(document.styleSheets); | 
| 438         document.addEventListener("load", this.onLoad.bind(this), true); | 429         document.addEventListener("load", this.onLoad.bind(this), true); | 
| 439       } | 430       } | 
|  | 431       reportError = oldReportError; | 
| 440     }); | 432     }); | 
| 441   } | 433   } | 
| 442 }; | 434 }; | 
| LEFT | RIGHT | 
|---|