| 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-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 |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 187 try | 187 try |
| 188 { | 188 { |
| 189 return new RegExp(pattern, flags); | 189 return new RegExp(pattern, flags); |
| 190 } | 190 } |
| 191 catch (e) | 191 catch (e) |
| 192 { | 192 { |
| 193 } | 193 } |
| 194 return null; | 194 return null; |
| 195 } | 195 } |
| 196 | 196 |
| 197 function* evaluate(chain, index, prefix, subtree, styles) | 197 function* evaluate(chain, index, prefix, subtree, styles, targets) |
| 198 { | 198 { |
| 199 if (index >= chain.length) | 199 if (index >= chain.length) |
| 200 { | 200 { |
| 201 yield prefix; | 201 yield prefix; |
| 202 return; | 202 return; |
| 203 } | 203 } |
| 204 for (let [selector, element] of | 204 for (let [selector, element] of |
| 205 chain[index].getSelectors(prefix, subtree, styles)) | 205 chain[index].getSelectors(prefix, subtree, styles, targets)) |
| 206 { | 206 { |
| 207 if (selector == null) | 207 if (selector == null) |
| 208 yield null; | 208 yield null; |
| 209 else | 209 else |
| 210 yield* evaluate(chain, index + 1, selector, element, styles); | 210 yield* evaluate(chain, index + 1, selector, element, styles, targets); |
| 211 } | 211 } |
| 212 // Just in case the getSelectors() generator above had to run some heavy | 212 // Just in case the getSelectors() generator above had to run some heavy |
| 213 // document.querySelectorAll() call which didn't produce any results, make | 213 // document.querySelectorAll() call which didn't produce any results, make |
| 214 // sure there is at least one point where execution can pause. | 214 // sure there is at least one point where execution can pause. |
| 215 yield null; | 215 yield null; |
| 216 } | 216 } |
| 217 | 217 |
| 218 function PlainSelector(selector) | 218 function PlainSelector(selector) |
| 219 { | 219 { |
| 220 this._selector = selector; | 220 this._selector = selector; |
| 221 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); | 221 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); |
| 222 } | 222 } |
| 223 | 223 |
| 224 PlainSelector.prototype = { | 224 PlainSelector.prototype = { |
| 225 /** | 225 /** |
| 226 * Generator function returning a pair of selector | 226 * Generator function returning a pair of selector |
| 227 * string and subtree. | 227 * string and subtree. |
| 228 * @param {string} prefix the prefix for the selector. | 228 * @param {string} prefix the prefix for the selector. |
| 229 * @param {Node} subtree the subtree we work on. | 229 * @param {Node} subtree the subtree we work on. |
| 230 * @param {StringifiedStyle[]} styles the stringified style objects. | 230 * @param {StringifiedStyle[]} styles the stringified style objects. |
| 231 * @param {Node[]} [targets] the nodes we are interested in. |
| 231 */ | 232 */ |
| 232 *getSelectors(prefix, subtree, styles) | 233 *getSelectors(prefix, subtree, styles, targets) |
| 233 { | 234 { |
| 234 yield [prefix + this._selector, subtree]; | 235 yield [prefix + this._selector, subtree]; |
| 235 } | 236 } |
| 236 }; | 237 }; |
| 237 | 238 |
| 238 const incompletePrefixRegexp = /[\s>+~]$/; | 239 const incompletePrefixRegexp = /[\s>+~]$/; |
| 239 | 240 |
| 240 function HasSelector(selectors) | 241 function HasSelector(selectors) |
| 241 { | 242 { |
| 242 this._innerSelectors = selectors; | 243 this._innerSelectors = selectors; |
| (...skipping 15 matching lines...) Expand all Loading... |
| 258 ); | 259 ); |
| 259 }, | 260 }, |
| 260 | 261 |
| 261 get maybeDependsOnAttributes() | 262 get maybeDependsOnAttributes() |
| 262 { | 263 { |
| 263 return this._innerSelectors.some( | 264 return this._innerSelectors.some( |
| 264 selector => selector.maybeDependsOnAttributes | 265 selector => selector.maybeDependsOnAttributes |
| 265 ); | 266 ); |
| 266 }, | 267 }, |
| 267 | 268 |
| 268 *getSelectors(prefix, subtree, styles) | 269 *getSelectors(prefix, subtree, styles, targets) |
| 269 { | 270 { |
| 270 for (let element of this.getElements(prefix, subtree, styles)) | 271 for (let element of this.getElements(prefix, subtree, styles, targets)) |
| 271 yield [makeSelector(element, ""), element]; | 272 yield [makeSelector(element, ""), element]; |
| 272 }, | 273 }, |
| 273 | 274 |
| 274 /** | 275 /** |
| 275 * Generator function returning selected elements. | 276 * Generator function returning selected elements. |
| 276 * @param {string} prefix the prefix for the selector. | 277 * @param {string} prefix the prefix for the selector. |
| 277 * @param {Node} subtree the subtree we work on. | 278 * @param {Node} subtree the subtree we work on. |
| 278 * @param {StringifiedStyle[]} styles the stringified style objects. | 279 * @param {StringifiedStyle[]} styles the stringified style objects. |
| 280 * @param {Node[]} [targets] the nodes we are interested in. |
| 279 */ | 281 */ |
| 280 *getElements(prefix, subtree, styles) | 282 *getElements(prefix, subtree, styles, targets) |
| 281 { | 283 { |
| 282 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 284 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
| 283 prefix + "*" : prefix; | 285 prefix + "*" : prefix; |
| 284 let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 286 let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
| 285 if (elements) | 287 if (elements) |
| 286 { | 288 { |
| 287 for (let element of elements) | 289 for (let element of elements) |
| 288 { | 290 { |
| 289 let iter = evaluate(this._innerSelectors, 0, "", element, styles); | 291 // If the element is neither an ancestor nor a descendant of one of the |
| 292 // targets, we can skip it. |
| 293 if (targets && !targets.some(target => element.contains(target) || |
| 294 target.contains(element))) |
| 295 { |
| 296 yield null; |
| 297 continue; |
| 298 } |
| 299 |
| 300 let iter = evaluate(this._innerSelectors, 0, "", element, styles, |
| 301 targets); |
| 290 for (let selector of iter) | 302 for (let selector of iter) |
| 291 { | 303 { |
| 292 if (selector == null) | 304 if (selector == null) |
| 293 yield null; | 305 yield null; |
| 294 else if (scopedQuerySelector(element, selector)) | 306 else if (scopedQuerySelector(element, selector)) |
| 295 yield element; | 307 yield element; |
| 296 } | 308 } |
| 297 yield null; | 309 yield null; |
| 298 } | 310 } |
| 299 } | 311 } |
| 300 } | 312 } |
| 301 }; | 313 }; |
| 302 | 314 |
| 303 function ContainsSelector(textContent) | 315 function ContainsSelector(textContent) |
| 304 { | 316 { |
| 305 this._regexp = makeRegExpParameter(textContent); | 317 this._regexp = makeRegExpParameter(textContent); |
| 306 } | 318 } |
| 307 | 319 |
| 308 ContainsSelector.prototype = { | 320 ContainsSelector.prototype = { |
| 309 requiresHiding: true, | 321 requiresHiding: true, |
| 310 dependsOnDOM: true, | 322 dependsOnDOM: true, |
| 311 dependsOnCharacterData: true, | 323 dependsOnCharacterData: true, |
| 312 | 324 |
| 313 *getSelectors(prefix, subtree, styles) | 325 *getSelectors(prefix, subtree, styles, targets) |
| 314 { | 326 { |
| 315 for (let element of this.getElements(prefix, subtree, styles)) | 327 for (let element of this.getElements(prefix, subtree, styles, targets)) |
| 316 yield [makeSelector(element, ""), subtree]; | 328 yield [makeSelector(element, ""), subtree]; |
| 317 }, | 329 }, |
| 318 | 330 |
| 319 *getElements(prefix, subtree, styles) | 331 *getElements(prefix, subtree, styles, targets) |
| 320 { | 332 { |
| 321 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 333 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
| 322 prefix + "*" : prefix; | 334 prefix + "*" : prefix; |
| 323 | 335 |
| 324 let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 336 let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
| 325 if (elements) | 337 if (elements) |
| 326 { | 338 { |
| 327 for (let element of elements) | 339 for (let element of elements) |
| 328 { | 340 { |
| 341 if (targets && !targets.some(target => element.contains(target) || |
| 342 target.contains(element))) |
| 343 { |
| 344 yield null; |
| 345 continue; |
| 346 } |
| 347 |
| 329 if (this._regexp && this._regexp.test(element.textContent)) | 348 if (this._regexp && this._regexp.test(element.textContent)) |
| 330 yield element; | 349 yield element; |
| 331 else | 350 else |
| 332 yield null; | 351 yield null; |
| 333 } | 352 } |
| 334 } | 353 } |
| 335 } | 354 } |
| 336 }; | 355 }; |
| 337 | 356 |
| 338 function PropsSelector(propertyExpression) | 357 function PropsSelector(propertyExpression) |
| (...skipping 26 matching lines...) Expand all Loading... |
| 365 { | 384 { |
| 366 subSelector = subSelector.substr(1); | 385 subSelector = subSelector.substr(1); |
| 367 } | 386 } |
| 368 let idx = subSelector.lastIndexOf("::"); | 387 let idx = subSelector.lastIndexOf("::"); |
| 369 if (idx != -1) | 388 if (idx != -1) |
| 370 subSelector = subSelector.substr(0, idx); | 389 subSelector = subSelector.substr(0, idx); |
| 371 yield prefix + subSelector; | 390 yield prefix + subSelector; |
| 372 } | 391 } |
| 373 }, | 392 }, |
| 374 | 393 |
| 375 *getSelectors(prefix, subtree, styles) | 394 *getSelectors(prefix, subtree, styles, targets) |
| 376 { | 395 { |
| 377 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 396 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |
| 378 yield [selector, subtree]; | 397 yield [selector, subtree]; |
| 379 } | 398 } |
| 380 }; | 399 }; |
| 381 | 400 |
| 382 function isSelectorHidingOnlyPattern(pattern) | 401 function isSelectorHidingOnlyPattern(pattern) |
| 383 { | 402 { |
| 384 return pattern.selectors.some(s => s.preferHideWithSelector) && | 403 return pattern.selectors.some(s => s.preferHideWithSelector) && |
| 385 !pattern.selectors.some(s => s.requiresHiding); | 404 !pattern.selectors.some(s => s.requiresHiding); |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 440 | 459 |
| 441 // There are only 3 types of mutations: "attributes", "characterData", and | 460 // There are only 3 types of mutations: "attributes", "characterData", and |
| 442 // "childList". | 461 // "childList". |
| 443 if (types.size == 3) | 462 if (types.size == 3) |
| 444 break; | 463 break; |
| 445 } | 464 } |
| 446 | 465 |
| 447 return types; | 466 return types; |
| 448 } | 467 } |
| 449 | 468 |
| 469 function extractMutationTargets(mutations) |
| 470 { |
| 471 if (!mutations) |
| 472 return null; |
| 473 |
| 474 let targets = new Set(); |
| 475 |
| 476 for (let mutation of mutations) |
| 477 { |
| 478 if (mutation.type == "childList") |
| 479 { |
| 480 // When new nodes are added, we're interested in the added nodes rather |
| 481 // than the parent. |
| 482 for (let node of mutation.addedNodes) |
| 483 targets.add(node); |
| 484 |
| 485 // Ideally we would also be interested in removed nodes, but since we |
| 486 // never unhide an element once hidden we can simply ignore any removed |
| 487 // nodes. Note that this will change once we start using CSS selectors |
| 488 // for -abp-has and -abp-contains, i.e. we'll have to remove the |
| 489 // selectors for any removed nodes. |
| 490 } |
| 491 else |
| 492 { |
| 493 targets.add(mutation.target); |
| 494 } |
| 495 } |
| 496 |
| 497 return [...targets]; |
| 498 } |
| 499 |
| 450 function filterPatterns(patterns, {stylesheets, mutations}) | 500 function filterPatterns(patterns, {stylesheets, mutations}) |
| 451 { | 501 { |
| 452 if (!stylesheets && !mutations) | 502 if (!stylesheets && !mutations) |
| 453 return patterns.slice(); | 503 return patterns.slice(); |
| 454 | 504 |
| 455 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; | 505 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; |
| 456 | 506 |
| 457 return patterns.filter( | 507 return patterns.filter( |
| 458 pattern => (stylesheets && patternDependsOnStyles(pattern)) || | 508 pattern => (stylesheets && patternDependsOnStyles(pattern)) || |
| 459 (mutations && patternDependsOnDOM(pattern) && | 509 (mutations && patternDependsOnDOM(pattern) && |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 610 | 660 |
| 611 for (let rule of rules) | 661 for (let rule of rules) |
| 612 { | 662 { |
| 613 if (rule.type != rule.STYLE_RULE) | 663 if (rule.type != rule.STYLE_RULE) |
| 614 continue; | 664 continue; |
| 615 | 665 |
| 616 cssStyles.push(stringifyStyle(rule)); | 666 cssStyles.push(stringifyStyle(rule)); |
| 617 } | 667 } |
| 618 } | 668 } |
| 619 | 669 |
| 670 let targets = extractMutationTargets(mutations); |
| 671 |
| 620 let pattern = null; | 672 let pattern = null; |
| 621 let generator = null; | 673 let generator = null; |
| 622 | 674 |
| 623 let processPatterns = () => | 675 let processPatterns = () => |
| 624 { | 676 { |
| 625 let cycleStart = performance.now(); | 677 let cycleStart = performance.now(); |
| 626 | 678 |
| 627 if (!pattern) | 679 if (!pattern) |
| 628 { | 680 { |
| 629 if (!patterns.length) | 681 if (!patterns.length) |
| 630 { | 682 { |
| 631 this.addSelectorsFunc(selectors, selectorFilters); | 683 this.addSelectorsFunc(selectors, selectorFilters); |
| 632 this.hideElemsFunc(elements, elementFilters); | 684 this.hideElemsFunc(elements, elementFilters); |
| 633 if (typeof done == "function") | 685 if (typeof done == "function") |
| 634 done(); | 686 done(); |
| 635 return; | 687 return; |
| 636 } | 688 } |
| 637 | 689 |
| 638 pattern = patterns.shift(); | 690 pattern = patterns.shift(); |
| 639 | 691 |
| 640 generator = evaluate(pattern.selectors, 0, "", | 692 generator = evaluate(pattern.selectors, 0, "", |
| 641 this.document, cssStyles); | 693 this.document, cssStyles, targets); |
| 642 } | 694 } |
| 643 for (let selector of generator) | 695 for (let selector of generator) |
| 644 { | 696 { |
| 645 if (selector != null) | 697 if (selector != null) |
| 646 { | 698 { |
| 647 if (isSelectorHidingOnlyPattern(pattern)) | 699 if (isSelectorHidingOnlyPattern(pattern)) |
| 648 { | 700 { |
| 649 selectors.push(selector); | 701 selectors.push(selector); |
| 650 selectorFilters.push(pattern.text); | 702 selectorFilters.push(pattern.text); |
| 651 } | 703 } |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 801 characterData: shouldObserveCharacterData(this.patterns), | 853 characterData: shouldObserveCharacterData(this.patterns), |
| 802 subtree: true | 854 subtree: true |
| 803 } | 855 } |
| 804 ); | 856 ); |
| 805 this.document.addEventListener("load", this.onLoad.bind(this), true); | 857 this.document.addEventListener("load", this.onLoad.bind(this), true); |
| 806 } | 858 } |
| 807 } | 859 } |
| 808 }; | 860 }; |
| 809 | 861 |
| 810 exports.ElemHideEmulation = ElemHideEmulation; | 862 exports.ElemHideEmulation = ElemHideEmulation; |
| OLD | NEW |