| Left: | ||
| Right: |
| 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 456 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 467 function shouldObserveAttributes(patterns) | 467 function shouldObserveAttributes(patterns) |
| 468 { | 468 { |
| 469 return patterns.some(pattern => pattern.maybeDependsOnAttributes); | 469 return patterns.some(pattern => pattern.maybeDependsOnAttributes); |
| 470 } | 470 } |
| 471 | 471 |
| 472 function shouldObserveCharacterData(patterns) | 472 function shouldObserveCharacterData(patterns) |
| 473 { | 473 { |
| 474 return patterns.some(pattern => pattern.dependsOnCharacterData); | 474 return patterns.some(pattern => pattern.dependsOnCharacterData); |
| 475 } | 475 } |
| 476 | 476 |
| 477 function CSSSelectorManager(root) | |
| 478 { | |
| 479 this._root = root; | |
| 480 | |
| 481 this.selectors = []; | |
| 482 this.filters = []; | |
| 483 | |
| 484 this._lookup = new Set(); | |
| 485 | |
| 486 this._extended = false; | |
| 487 } | |
| 488 | |
| 489 CSSSelectorManager.prototype = { | |
| 490 add(selectors, filters) | |
| 491 { | |
| 492 for (let i = 0; i < selectors.length; i++) | |
| 493 { | |
| 494 this.selectors.push(selectors[i]); | |
| 495 this.filters.push(filters[i]); | |
| 496 | |
| 497 this._lookup.add(selectors[i]); | |
| 498 | |
| 499 if (!this._extended && selectors[i].startsWith(":root > ")) | |
| 500 this._extended = true; | |
| 501 } | |
| 502 }, | |
| 503 | |
| 504 has(selector) | |
| 505 { | |
| 506 return this._lookup.has(selector); | |
| 507 }, | |
| 508 | |
| 509 setAt(index, selector) | |
| 510 { | |
| 511 this._lookup.delete(this.selectors[index]); | |
| 512 this._lookup.add(selector); | |
| 513 | |
| 514 this.selectors[index] = selector; | |
| 515 }, | |
| 516 | |
| 517 deleteAt(index) | |
| 518 { | |
| 519 this._lookup.delete(this.selectors[index]); | |
| 520 | |
| 521 this.selectors.splice(index, 1); | |
| 522 this.filters.splice(index, 1); | |
| 523 }, | |
| 524 | |
| 525 update(mutations) | |
| 526 { | |
| 527 // If there are no extended CSS selectors (i.e. :-abp-*), there's nothing | |
| 528 // to update. | |
| 529 if (!this._extended || this.selectors.length == 0) | |
|
Manish Jethani
2018/03/21 15:35:26
This is useful. In EasyList the number of sites wi
| |
| 530 return false; | |
| 531 | |
| 532 let changed = false; | |
| 533 | |
| 534 for (let mutation of mutations) | |
| 535 { | |
| 536 if (this._updateForMutation(mutation)) | |
| 537 changed = true; | |
| 538 } | |
| 539 | |
| 540 return changed; | |
| 541 }, | |
| 542 | |
| 543 _updateForMutation(mutation) | |
| 544 { | |
| 545 let {target} = mutation; | |
| 546 | |
| 547 // Existing selectors aren't affected by non-childList mutations. | |
| 548 if (mutation.type != "childList" || !this._root.contains(target)) | |
| 549 return false; | |
| 550 | |
| 551 // Find the mutation index, i.e. the index in the parent element where the | |
| 552 // mutation has occurred. | |
| 553 let index = 0; | |
| 554 let ref = mutation.previousSibling; | |
| 555 if (ref) | |
| 556 { | |
| 557 if (!(ref instanceof Element)) | |
| 558 ref = ref.previousElementSibling; | |
| 559 | |
| 560 if (ref) | |
| 561 index = positionInParent(ref); | |
| 562 } | |
| 563 | |
| 564 // Count the number of elements added and removed. Both can be non-zero in | |
| 565 // some cases, e.g. when Element.innerHTML is set on the parent element to | |
| 566 // overwrite all the existing child nodes. | |
| 567 let numAddedElements = 0; | |
| 568 let numRemovedElements = 0; | |
| 569 | |
| 570 for (let node of mutation.addedNodes) | |
| 571 { | |
| 572 if (node instanceof Element) | |
| 573 numAddedElements++; | |
| 574 } | |
| 575 | |
| 576 for (let node of mutation.removedNodes) | |
| 577 { | |
| 578 if (node instanceof Element) | |
| 579 numRemovedElements++; | |
| 580 } | |
| 581 | |
| 582 let changed = false; | |
| 583 | |
| 584 // Go through all the selectors and find any that match the mutation target. | |
| 585 let targetSelector = makeSelector(target) + " > "; | |
| 586 for (let i = 0; i < this.selectors.length; i++) | |
| 587 { | |
| 588 let selector = this.selectors[i]; | |
| 589 if (selector.startsWith(targetSelector)) | |
| 590 { | |
| 591 // If there's a match, extract the one-based child index. | |
| 592 let subSelector = selector.substring(targetSelector.length); | |
| 593 let match = /^[^:]*:nth-child\((\d+)\)/.exec(subSelector); | |
| 594 let nthIndex = match ? +match[1] : 0; | |
| 595 | |
| 596 // If the child being targeted by the selector is before the mutation | |
| 597 // index (i.e. the one-based child index is not affected), do nothing. | |
| 598 if (nthIndex <= index) | |
| 599 continue; | |
| 600 | |
| 601 nthIndex -= numRemovedElements; | |
| 602 | |
| 603 // After adjusting for the number of removed elements, if the one-based | |
| 604 // child index appears to be lower than the mutation index, it means the | |
| 605 // child got removed. In that case we must delete the selector. | |
| 606 if (nthIndex <= index) | |
| 607 { | |
| 608 this.deleteAt(i--); | |
| 609 changed = true; | |
| 610 continue; | |
| 611 } | |
| 612 | |
| 613 nthIndex += numAddedElements; | |
| 614 | |
| 615 // If the one-based child index has changed, we must update the | |
| 616 // selector. | |
| 617 if (nthIndex != +match[1]) | |
| 618 { | |
| 619 this.setAt(i, targetSelector + | |
| 620 subSelector.replace(match[1], nthIndex)); | |
| 621 changed = true; | |
| 622 } | |
| 623 } | |
| 624 } | |
| 625 | |
| 626 return changed; | |
| 627 } | |
| 628 }; | |
| 629 | |
| 477 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 630 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
| 478 { | 631 { |
| 479 this.document = document; | 632 this.document = document; |
| 480 this.addSelectorsFunc = addSelectorsFunc; | 633 this.addSelectorsFunc = addSelectorsFunc; |
| 481 this.hideElemsFunc = hideElemsFunc; | 634 this.hideElemsFunc = hideElemsFunc; |
| 482 this.observer = new MutationObserver(this.observe.bind(this)); | 635 this.observer = new MutationObserver(this.observe.bind(this)); |
| 483 this.useInlineStyles = true; | 636 this.useInlineStyles = true; |
| 637 this.css = new CSSSelectorManager(this.document); | |
| 484 } | 638 } |
| 485 | 639 |
| 486 ElemHideEmulation.prototype = { | 640 ElemHideEmulation.prototype = { |
| 487 isSameOrigin(stylesheet) | 641 isSameOrigin(stylesheet) |
| 488 { | 642 { |
| 489 try | 643 try |
| 490 { | 644 { |
| 491 return new URL(stylesheet.href).origin == this.document.location.origin; | 645 return new URL(stylesheet.href).origin == this.document.location.origin; |
| 492 } | 646 } |
| 493 catch (e) | 647 catch (e) |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 572 * made reprocessing necessary. This parameter shouldn't be passed in for | 726 * made reprocessing necessary. This parameter shouldn't be passed in for |
| 573 * the initial processing, the entire document will be considered | 727 * the initial processing, the entire document will be considered |
| 574 * then and all rules, including the ones not dependent on the DOM. | 728 * then and all rules, including the ones not dependent on the DOM. |
| 575 * @param {function} [done] | 729 * @param {function} [done] |
| 576 * Callback to call when done. | 730 * Callback to call when done. |
| 577 */ | 731 */ |
| 578 _addSelectors(stylesheets, mutations, done) | 732 _addSelectors(stylesheets, mutations, done) |
| 579 { | 733 { |
| 580 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); | 734 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); |
| 581 | 735 |
| 736 if (patterns.length == 0) | |
| 737 return; | |
| 738 | |
| 582 let selectors = []; | 739 let selectors = []; |
| 583 let selectorFilters = []; | 740 let selectorFilters = []; |
| 584 | 741 |
| 585 let elements = []; | 742 let elements = []; |
| 586 let elementFilters = []; | 743 let elementFilters = []; |
| 587 | 744 |
| 588 let cssStyles = []; | 745 let cssStyles = []; |
| 589 | 746 |
| 590 // If neither any style sheets nor any DOM mutations have been specified, | 747 // If neither any style sheets nor any DOM mutations have been specified, |
| 591 // do full processing. | 748 // do full processing. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 626 | 783 |
| 627 let processPatterns = () => | 784 let processPatterns = () => |
| 628 { | 785 { |
| 629 let cycleStart = performance.now(); | 786 let cycleStart = performance.now(); |
| 630 | 787 |
| 631 if (!pattern) | 788 if (!pattern) |
| 632 { | 789 { |
| 633 if (!patterns.length) | 790 if (!patterns.length) |
| 634 { | 791 { |
| 635 if (selectors.length > 0) | 792 if (selectors.length > 0) |
| 636 this.addSelectorsFunc(selectors, selectorFilters); | 793 { |
| 794 this.css.add(selectors, selectorFilters); | |
| 795 this.addSelectorsFunc(this.css.selectors, this.css.filters); | |
| 796 } | |
| 797 | |
| 637 if (elements.length > 0) | 798 if (elements.length > 0) |
| 638 this.hideElemsFunc(elements, elementFilters); | 799 this.hideElemsFunc(elements, elementFilters); |
| 800 | |
| 639 if (typeof done == "function") | 801 if (typeof done == "function") |
| 640 done(); | 802 done(); |
| 641 return; | 803 return; |
| 642 } | 804 } |
| 643 | 805 |
| 644 pattern = patterns.shift(); | 806 pattern = patterns.shift(); |
| 645 | 807 |
| 646 generator = evaluate(pattern.selectors, 0, "", | 808 generator = evaluate(pattern.selectors, 0, "", |
| 647 this.document, cssStyles); | 809 this.document, cssStyles); |
| 648 } | 810 } |
| 649 for (let selector of generator) | 811 for (let selector of generator) |
| 650 { | 812 { |
| 651 if (selector != null) | 813 if (selector != null) |
| 652 { | 814 { |
| 653 if (!this.useInlineStyles || | 815 if (!this.useInlineStyles || |
| 654 pattern.isSelectorHidingOnlyPattern()) | 816 pattern.isSelectorHidingOnlyPattern()) |
| 655 { | 817 { |
| 656 selectors.push(selector); | 818 if (!this.css.has(selector)) |
| 657 selectorFilters.push(pattern.text); | 819 { |
| 820 selectors.push(selector); | |
| 821 selectorFilters.push(pattern.text); | |
| 822 } | |
| 658 } | 823 } |
| 659 else | 824 else |
| 660 { | 825 { |
| 661 for (let element of this.document.querySelectorAll(selector)) | 826 for (let element of this.document.querySelectorAll(selector)) |
| 662 { | 827 { |
| 663 elements.push(element); | 828 elements.push(element); |
| 664 elementFilters.push(pattern.text); | 829 elementFilters.push(pattern.text); |
| 665 } | 830 } |
| 666 } | 831 } |
| 667 } | 832 } |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 777 | 942 |
| 778 onLoad(event) | 943 onLoad(event) |
| 779 { | 944 { |
| 780 let stylesheet = event.target.sheet; | 945 let stylesheet = event.target.sheet; |
| 781 if (stylesheet) | 946 if (stylesheet) |
| 782 this.queueFiltering([stylesheet]); | 947 this.queueFiltering([stylesheet]); |
| 783 }, | 948 }, |
| 784 | 949 |
| 785 observe(mutations) | 950 observe(mutations) |
| 786 { | 951 { |
| 952 if (this.css.update(mutations)) | |
| 953 this.addSelectorsFunc(this.css.selectors, this.css.filters); | |
|
hub
2018/03/23 15:54:25
My concern here is that the intent of queueFilteri
Manish Jethani
2018/03/23 16:55:33
The thing is that I'm trying to avoid running full
| |
| 954 | |
| 787 this.queueFiltering(null, mutations); | 955 this.queueFiltering(null, mutations); |
| 788 }, | 956 }, |
| 789 | 957 |
| 790 apply(patterns) | 958 apply(patterns) |
| 791 { | 959 { |
| 792 this.patterns = []; | 960 this.patterns = []; |
| 793 for (let pattern of patterns) | 961 for (let pattern of patterns) |
| 794 { | 962 { |
| 795 let selectors = this.parseSelector(pattern.selector); | 963 let selectors = this.parseSelector(pattern.selector); |
| 796 if (selectors != null && selectors.length > 0) | 964 if (selectors != null && selectors.length > 0) |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 808 characterData: shouldObserveCharacterData(this.patterns), | 976 characterData: shouldObserveCharacterData(this.patterns), |
| 809 subtree: true | 977 subtree: true |
| 810 } | 978 } |
| 811 ); | 979 ); |
| 812 this.document.addEventListener("load", this.onLoad.bind(this), true); | 980 this.document.addEventListener("load", this.onLoad.bind(this), true); |
| 813 } | 981 } |
| 814 } | 982 } |
| 815 }; | 983 }; |
| 816 | 984 |
| 817 exports.ElemHideEmulation = ElemHideEmulation; | 985 exports.ElemHideEmulation = ElemHideEmulation; |
| OLD | NEW |