| 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-present eyeo GmbH | 3  * Copyright (C) 2006-present 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 "use strict"; | 18 "use strict"; | 
| 19 | 19 | 
| 20 const {filterToRegExp, splitSelector} = require("../common"); | 20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common"); | 
| 21 | 21 | 
| 22 let MIN_INVOCATION_INTERVAL = 3000; | 22 let MIN_INVOCATION_INTERVAL = 3000; | 
| 23 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; | 23 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; | 
| 24 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 24 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 
| 25 | 25 | 
| 26 /** Return position of node from parent. | 26 /** Return position of node from parent. | 
| 27  * @param {Node} node the node to find the position of. | 27  * @param {Node} node the node to find the position of. | 
| 28  * @return {number} One-based index like for :nth-child(), or 0 on error. | 28  * @return {number} One-based index like for :nth-child(), or 0 on error. | 
| 29  */ | 29  */ | 
| 30 function positionInParent(node) | 30 function positionInParent(node) | 
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 115     let priority = rule.style.getPropertyPriority(property); | 115     let priority = rule.style.getPropertyPriority(property); | 
| 116     styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`); | 116     styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`); | 
| 117   } | 117   } | 
| 118   styles.sort(); | 118   styles.sort(); | 
| 119   return { | 119   return { | 
| 120     style: styles.join(" "), | 120     style: styles.join(" "), | 
| 121     subSelectors: splitSelector(rule.selectorText) | 121     subSelectors: splitSelector(rule.selectorText) | 
| 122   }; | 122   }; | 
| 123 } | 123 } | 
| 124 | 124 | 
|  | 125 let scopeSupported = null; | 
|  | 126 | 
|  | 127 function tryQuerySelector(subtree, selector, all) | 
|  | 128 { | 
|  | 129   let elements = null; | 
|  | 130   try | 
|  | 131   { | 
|  | 132     elements = all ? subtree.querySelectorAll(selector) : | 
|  | 133       subtree.querySelector(selector); | 
|  | 134     scopeSupported = true; | 
|  | 135   } | 
|  | 136   catch (e) | 
|  | 137   { | 
|  | 138     // Edge doesn't support ":scope" | 
|  | 139     scopeSupported = false; | 
|  | 140   } | 
|  | 141   return elements; | 
|  | 142 } | 
|  | 143 | 
|  | 144 /** | 
|  | 145  * Query selector. If it is relative, will try :scope. | 
|  | 146  * @param {Node} subtree the element to query selector | 
|  | 147  * @param {string} selector the selector to query | 
|  | 148  * @param {bool} [all=false] true to perform querySelectorAll() | 
|  | 149  * @returns {?(Node|NodeList)} result of the query. null in case of error. | 
|  | 150  */ | 
|  | 151 function scopedQuerySelector(subtree, selector, all) | 
|  | 152 { | 
|  | 153   if (selector[0] == ">") | 
|  | 154   { | 
|  | 155     selector = ":scope" + selector; | 
|  | 156     if (scopeSupported) | 
|  | 157     { | 
|  | 158       return all ? subtree.querySelectorAll(selector) : | 
|  | 159         subtree.querySelector(selector); | 
|  | 160     } | 
|  | 161     if (scopeSupported == null) | 
|  | 162       return tryQuerySelector(subtree, selector, all); | 
|  | 163     return null; | 
|  | 164   } | 
|  | 165   return all ? subtree.querySelectorAll(selector) : | 
|  | 166     subtree.querySelector(selector); | 
|  | 167 } | 
|  | 168 | 
|  | 169 function scopedQuerySelectorAll(subtree, selector) | 
|  | 170 { | 
|  | 171   return scopedQuerySelector(subtree, selector, true); | 
|  | 172 } | 
|  | 173 | 
| 125 const regexpRegexp = /^\/(.*)\/([im]*)$/; | 174 const regexpRegexp = /^\/(.*)\/([im]*)$/; | 
| 126 | 175 | 
| 127 /** | 176 /** | 
| 128  * Make a regular expression from a text argument. If it can be parsed as a | 177  * Make a regular expression from a text argument. If it can be parsed as a | 
| 129  * regular expression, parse it and the flags. | 178  * regular expression, parse it and the flags. | 
| 130  * @param {string} text the text argument. | 179  * @param {string} text the text argument. | 
| 131  * @param {?string} flags the regexp flags passed to the RegExp constructor: |  | 
| 132  * it overrides flags passed in the text argument. |  | 
| 133  * @return {?RegExp} a RegExp object or null in case of error. | 180  * @return {?RegExp} a RegExp object or null in case of error. | 
| 134  */ | 181  */ | 
| 135 function makeRegExpParameter(text, flags) | 182 function makeRegExpParameter(text) | 
| 136 { | 183 { | 
| 137   let regexpString = null; | 184   let [, pattern, flags] = | 
| 138   let match = regexpRegexp.exec(text); | 185       regexpRegexp.exec(text) || [undefined, textToRegExp(text)]; | 
| 139   if (match) |  | 
| 140   { |  | 
| 141     regexpString = match[1].replace("\\7B ", "{").replace("\\7D ", "}"); |  | 
| 142     if (!flags) |  | 
| 143       flags = match[2]; |  | 
| 144   } |  | 
| 145   else |  | 
| 146     regexpString = filterToRegExp(text); |  | 
| 147 | 186 | 
| 148   try | 187   try | 
| 149   { | 188   { | 
| 150     return new RegExp(regexpString, flags); | 189     return new RegExp(pattern, flags); | 
| 151   } | 190   } | 
| 152   catch (e) | 191   catch (e) | 
| 153   { | 192   { | 
| 154   } | 193   } | 
| 155   return null; | 194   return null; | 
| 156 } | 195 } | 
| 157 | 196 | 
| 158 function* evaluate(chain, index, prefix, subtree, styles) | 197 function* evaluate(chain, index, prefix, subtree, styles) | 
| 159 { | 198 { | 
| 160   if (index >= chain.length) | 199   if (index >= chain.length) | 
| (...skipping 28 matching lines...) Expand all  Loading... | 
| 189    * @param {Node} subtree the subtree we work on. | 228    * @param {Node} subtree the subtree we work on. | 
| 190    * @param {StringifiedStyle[]} styles the stringified style objects. | 229    * @param {StringifiedStyle[]} styles the stringified style objects. | 
| 191    */ | 230    */ | 
| 192   *getSelectors(prefix, subtree, styles) | 231   *getSelectors(prefix, subtree, styles) | 
| 193   { | 232   { | 
| 194     yield [prefix + this._selector, subtree]; | 233     yield [prefix + this._selector, subtree]; | 
| 195   } | 234   } | 
| 196 }; | 235 }; | 
| 197 | 236 | 
| 198 const incompletePrefixRegexp = /[\s>+~]$/; | 237 const incompletePrefixRegexp = /[\s>+~]$/; | 
| 199 const relativeSelectorRegexp = /^[>+~]/; |  | 
| 200 | 238 | 
| 201 function HasSelector(selectors) | 239 function HasSelector(selectors) | 
| 202 { | 240 { | 
| 203   this._innerSelectors = selectors; | 241   this._innerSelectors = selectors; | 
| 204 } | 242 } | 
| 205 | 243 | 
| 206 HasSelector.prototype = { | 244 HasSelector.prototype = { | 
| 207   requiresHiding: true, | 245   requiresHiding: true, | 
| 208 | 246 | 
| 209   get dependsOnStyles() | 247   get dependsOnStyles() | 
| (...skipping 10 matching lines...) Expand all  Loading... | 
| 220   /** | 258   /** | 
| 221    * Generator function returning selected elements. | 259    * Generator function returning selected elements. | 
| 222    * @param {string} prefix the prefix for the selector. | 260    * @param {string} prefix the prefix for the selector. | 
| 223    * @param {Node} subtree the subtree we work on. | 261    * @param {Node} subtree the subtree we work on. | 
| 224    * @param {StringifiedStyle[]} styles the stringified style objects. | 262    * @param {StringifiedStyle[]} styles the stringified style objects. | 
| 225    */ | 263    */ | 
| 226   *getElements(prefix, subtree, styles) | 264   *getElements(prefix, subtree, styles) | 
| 227   { | 265   { | 
| 228     let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 266     let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 
| 229         prefix + "*" : prefix; | 267         prefix + "*" : prefix; | 
| 230     let elements = subtree.querySelectorAll(actualPrefix); | 268     let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 
| 231     for (let element of elements) | 269     if (elements) | 
| 232     { | 270     { | 
| 233       let iter = evaluate(this._innerSelectors, 0, "", element, styles); | 271       for (let element of elements) | 
| 234       for (let selector of iter) | 272       { | 
| 235       { | 273         let iter = evaluate(this._innerSelectors, 0, "", element, styles); | 
| 236         if (selector == null) | 274         for (let selector of iter) | 
| 237         { | 275         { | 
| 238           yield null; | 276           if (selector == null) | 
| 239           continue; | 277             yield null; | 
| 240         } | 278           else if (scopedQuerySelector(element, selector)) | 
| 241         if (relativeSelectorRegexp.test(selector)) |  | 
| 242           selector = ":scope" + selector; |  | 
| 243         try |  | 
| 244         { |  | 
| 245           if (element.querySelector(selector)) |  | 
| 246             yield element; | 279             yield element; | 
| 247         } | 280         } | 
| 248         catch (e) | 281         yield null; | 
| 249         { |  | 
| 250           // :scope isn't supported on Edge, ignore error caused by it. |  | 
| 251         } |  | 
| 252       } | 282       } | 
| 253       yield null; |  | 
| 254     } | 283     } | 
| 255   } | 284   } | 
| 256 }; | 285 }; | 
| 257 | 286 | 
| 258 function ContainsSelector(textContent) | 287 function ContainsSelector(textContent) | 
| 259 { | 288 { | 
| 260   this._regexp = makeRegExpParameter(textContent); | 289   this._regexp = makeRegExpParameter(textContent); | 
| 261 } | 290 } | 
| 262 | 291 | 
| 263 ContainsSelector.prototype = { | 292 ContainsSelector.prototype = { | 
| 264   requiresHiding: true, | 293   requiresHiding: true, | 
| 265 | 294 | 
| 266   *getSelectors(prefix, subtree, stylesheet) | 295   *getSelectors(prefix, subtree, stylesheet) | 
| 267   { | 296   { | 
| 268     for (let element of this.getElements(prefix, subtree, stylesheet)) | 297     for (let element of this.getElements(prefix, subtree, stylesheet)) | 
| 269       yield [makeSelector(element, ""), subtree]; | 298       yield [makeSelector(element, ""), subtree]; | 
| 270   }, | 299   }, | 
| 271 | 300 | 
| 272   *getElements(prefix, subtree, stylesheet) | 301   *getElements(prefix, subtree, stylesheet) | 
| 273   { | 302   { | 
| 274     let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 303     let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 
| 275         prefix + "*" : prefix; | 304         prefix + "*" : prefix; | 
| 276     let elements = subtree.querySelectorAll(actualPrefix); | 305 | 
| 277 | 306     let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 
| 278     for (let element of elements) | 307     if (elements) | 
| 279     { | 308     { | 
| 280       if (this._regexp && this._regexp.test(element.textContent)) | 309       for (let element of elements) | 
| 281         yield element; | 310       { | 
| 282       else | 311         if (this._regexp && this._regexp.test(element.textContent)) | 
| 283         yield null; | 312           yield element; | 
|  | 313         else | 
|  | 314           yield null; | 
|  | 315       } | 
| 284     } | 316     } | 
| 285   } | 317   } | 
| 286 }; | 318 }; | 
| 287 | 319 | 
| 288 function PropsSelector(propertyExpression) | 320 function PropsSelector(propertyExpression) | 
| 289 { | 321 { | 
| 290   this._regexp = makeRegExpParameter(propertyExpression, "i"); | 322   let regexpString; | 
|  | 323   if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 
|  | 324       propertyExpression[propertyExpression.length - 1] == "/") | 
|  | 325   { | 
|  | 326     regexpString = propertyExpression.slice(1, -1) | 
|  | 327       .replace("\\7B ", "{").replace("\\7D ", "}"); | 
|  | 328   } | 
|  | 329   else | 
|  | 330     regexpString = filterToRegExp(propertyExpression); | 
|  | 331 | 
|  | 332   this._regexp = new RegExp(regexpString, "i"); | 
| 291 } | 333 } | 
| 292 | 334 | 
| 293 PropsSelector.prototype = { | 335 PropsSelector.prototype = { | 
| 294   preferHideWithSelector: true, | 336   preferHideWithSelector: true, | 
| 295   dependsOnStyles: true, | 337   dependsOnStyles: true, | 
| 296 | 338 | 
| 297   *findPropsSelectors(styles, prefix, regexp) | 339   *findPropsSelectors(styles, prefix, regexp) | 
| 298   { | 340   { | 
| 299     for (let style of styles) | 341     for (let style of styles) | 
| 300       if (regexp && regexp.test(style.style)) | 342       if (regexp.test(style.style)) | 
| 301         for (let subSelector of style.subSelectors) | 343         for (let subSelector of style.subSelectors) | 
| 302         { | 344         { | 
| 303           if (subSelector.startsWith("*") && | 345           if (subSelector.startsWith("*") && | 
| 304               !incompletePrefixRegexp.test(prefix)) | 346               !incompletePrefixRegexp.test(prefix)) | 
| 305           { | 347           { | 
| 306             subSelector = subSelector.substr(1); | 348             subSelector = subSelector.substr(1); | 
| 307           } | 349           } | 
| 308           let idx = subSelector.lastIndexOf("::"); | 350           let idx = subSelector.lastIndexOf("::"); | 
| 309           if (idx != -1) | 351           if (idx != -1) | 
| 310             subSelector = subSelector.substr(0, idx); | 352             subSelector = subSelector.substr(0, idx); | 
| (...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 627           characterData: true, | 669           characterData: true, | 
| 628           subtree: true | 670           subtree: true | 
| 629         } | 671         } | 
| 630       ); | 672       ); | 
| 631       this.document.addEventListener("load", this.onLoad.bind(this), true); | 673       this.document.addEventListener("load", this.onLoad.bind(this), true); | 
| 632     } | 674     } | 
| 633   } | 675   } | 
| 634 }; | 676 }; | 
| 635 | 677 | 
| 636 exports.ElemHideEmulation = ElemHideEmulation; | 678 exports.ElemHideEmulation = ElemHideEmulation; | 
| LEFT | RIGHT | 
|---|