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 |
| 487 CSSSelectorManager.prototype = { |
| 488 add(selectors, filters) |
| 489 { |
| 490 for (let i = 0; i < selectors.length; i++) |
| 491 { |
| 492 this.selectors.push(selectors[i]); |
| 493 this.filters.push(filters[i]); |
| 494 |
| 495 this._lookup.add(selectors[i]); |
| 496 } |
| 497 }, |
| 498 |
| 499 has(selector) |
| 500 { |
| 501 return this._lookup.has(selector); |
| 502 }, |
| 503 |
| 504 setAt(index, selector) |
| 505 { |
| 506 this._lookup.delete(this.selectors[index]); |
| 507 this._lookup.add(selector); |
| 508 |
| 509 this.selectors[index] = selector; |
| 510 }, |
| 511 |
| 512 deleteAt(index) |
| 513 { |
| 514 this._lookup.delete(this.selectors[index]); |
| 515 |
| 516 this.selectors.splice(index, 1); |
| 517 this.filters.splice(index, 1); |
| 518 }, |
| 519 |
| 520 update(mutations) |
| 521 { |
| 522 let changed = false; |
| 523 |
| 524 for (let mutation of mutations) |
| 525 { |
| 526 if (this._updateForMutation(mutation)) |
| 527 changed = true; |
| 528 } |
| 529 |
| 530 return changed; |
| 531 }, |
| 532 |
| 533 _updateForMutation(mutation) |
| 534 { |
| 535 let {target} = mutation; |
| 536 |
| 537 // Existing selectors aren't affected by non-childList mutations. |
| 538 if (mutation.type != "childList" || !this._root.contains(target)) |
| 539 return false; |
| 540 |
| 541 // Find the mutation index, i.e. the index in the parent element where the |
| 542 // mutation has occurred. |
| 543 let index = 0; |
| 544 let ref = mutation.previousSibling; |
| 545 if (ref) |
| 546 { |
| 547 if (!(ref instanceof Element)) |
| 548 ref = ref.previousElementSibling; |
| 549 |
| 550 if (ref) |
| 551 index = positionInParent(ref); |
| 552 } |
| 553 |
| 554 // Count the number of elements added and removed. Both can be non-zero in |
| 555 // some cases, e.g. when Element.innerHTML is set on the parent element to |
| 556 // overwrite all the existing child nodes. |
| 557 let numAddedElements = 0; |
| 558 let numRemovedElements = 0; |
| 559 |
| 560 for (let node of mutation.addedNodes) |
| 561 { |
| 562 if (node instanceof Element) |
| 563 numAddedElements++; |
| 564 } |
| 565 |
| 566 for (let node of mutation.removedNodes) |
| 567 { |
| 568 if (node instanceof Element) |
| 569 numRemovedElements++; |
| 570 } |
| 571 |
| 572 let changed = false; |
| 573 |
| 574 // Go through all the selectors and find any that match the mutation target. |
| 575 let targetSelector = makeSelector(target) + " > "; |
| 576 for (let i = 0; i < this.selectors.length; i++) |
| 577 { |
| 578 let selector = this.selectors[i]; |
| 579 if (selector.startsWith(targetSelector)) |
| 580 { |
| 581 // If there's a match, extract the one-based child index. |
| 582 let subSelector = selector.substring(targetSelector.length); |
| 583 let match = /^[^:]*:nth-child\((\d+)\)/.exec(subSelector); |
| 584 let nthIndex = match ? +match[1] : 0; |
| 585 |
| 586 // If the child being targeted by the selector is before the mutation |
| 587 // index (i.e. the one-based child index is not affected), do nothing. |
| 588 if (nthIndex <= index) |
| 589 continue; |
| 590 |
| 591 nthIndex -= numRemovedElements; |
| 592 |
| 593 // After adjusting for the number of removed elements, if the one-based |
| 594 // child index appears to be lower than the mutation index, it means the |
| 595 // child got removed. In that case we must delete the selector. |
| 596 if (nthIndex <= index) |
| 597 { |
| 598 this.deleteAt(i--); |
| 599 changed = true; |
| 600 continue; |
| 601 } |
| 602 |
| 603 nthIndex += numAddedElements; |
| 604 |
| 605 // If the one-based child index has changed, we must update the |
| 606 // selector. |
| 607 if (nthIndex != +match[1]) |
| 608 { |
| 609 this.setAt(i, targetSelector + |
| 610 subSelector.replace(match[1], nthIndex)); |
| 611 changed = true; |
| 612 } |
| 613 } |
| 614 } |
| 615 |
| 616 return changed; |
| 617 } |
| 618 }; |
| 619 |
477 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 620 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
478 { | 621 { |
479 this.document = document; | 622 this.document = document; |
480 this.addSelectorsFunc = addSelectorsFunc; | 623 this.addSelectorsFunc = addSelectorsFunc; |
481 this.hideElemsFunc = hideElemsFunc; | 624 this.hideElemsFunc = hideElemsFunc; |
482 this.observer = new MutationObserver(this.observe.bind(this)); | 625 this.observer = new MutationObserver(this.observe.bind(this)); |
483 this.useInlineStyles = true; | 626 this.useInlineStyles = true; |
| 627 this.css = new CSSSelectorManager(this.document); |
484 } | 628 } |
485 | 629 |
486 ElemHideEmulation.prototype = { | 630 ElemHideEmulation.prototype = { |
487 isSameOrigin(stylesheet) | 631 isSameOrigin(stylesheet) |
488 { | 632 { |
489 try | 633 try |
490 { | 634 { |
491 return new URL(stylesheet.href).origin == this.document.location.origin; | 635 return new URL(stylesheet.href).origin == this.document.location.origin; |
492 } | 636 } |
493 catch (e) | 637 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 | 716 * made reprocessing necessary. This parameter shouldn't be passed in for |
573 * the initial processing, the entire document will be considered | 717 * the initial processing, the entire document will be considered |
574 * then and all rules, including the ones not dependent on the DOM. | 718 * then and all rules, including the ones not dependent on the DOM. |
575 * @param {function} [done] | 719 * @param {function} [done] |
576 * Callback to call when done. | 720 * Callback to call when done. |
577 */ | 721 */ |
578 _addSelectors(stylesheets, mutations, done) | 722 _addSelectors(stylesheets, mutations, done) |
579 { | 723 { |
580 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); | 724 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); |
581 | 725 |
| 726 if (patterns.length == 0) |
| 727 return; |
| 728 |
582 let selectors = []; | 729 let selectors = []; |
583 let selectorFilters = []; | 730 let selectorFilters = []; |
584 | 731 |
585 let elements = []; | 732 let elements = []; |
586 let elementFilters = []; | 733 let elementFilters = []; |
587 | 734 |
588 let cssStyles = []; | 735 let cssStyles = []; |
589 | 736 |
590 // If neither any style sheets nor any DOM mutations have been specified, | 737 // If neither any style sheets nor any DOM mutations have been specified, |
591 // do full processing. | 738 // do full processing. |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
626 | 773 |
627 let processPatterns = () => | 774 let processPatterns = () => |
628 { | 775 { |
629 let cycleStart = performance.now(); | 776 let cycleStart = performance.now(); |
630 | 777 |
631 if (!pattern) | 778 if (!pattern) |
632 { | 779 { |
633 if (!patterns.length) | 780 if (!patterns.length) |
634 { | 781 { |
635 if (selectors.length > 0) | 782 if (selectors.length > 0) |
636 this.addSelectorsFunc(selectors, selectorFilters); | 783 { |
| 784 this.css.add(selectors, selectorFilters); |
| 785 this.addSelectorsFunc(this.css.selectors, this.css.filters); |
| 786 } |
| 787 |
637 if (elements.length > 0) | 788 if (elements.length > 0) |
638 this.hideElemsFunc(elements, elementFilters); | 789 this.hideElemsFunc(elements, elementFilters); |
| 790 |
639 if (typeof done == "function") | 791 if (typeof done == "function") |
640 done(); | 792 done(); |
641 return; | 793 return; |
642 } | 794 } |
643 | 795 |
644 pattern = patterns.shift(); | 796 pattern = patterns.shift(); |
645 | 797 |
646 generator = evaluate(pattern.selectors, 0, "", | 798 generator = evaluate(pattern.selectors, 0, "", |
647 this.document, cssStyles); | 799 this.document, cssStyles); |
648 } | 800 } |
649 for (let selector of generator) | 801 for (let selector of generator) |
650 { | 802 { |
651 if (selector != null) | 803 if (selector != null) |
652 { | 804 { |
653 if (!this.useInlineStyles || | 805 if (!this.useInlineStyles || |
654 pattern.isSelectorHidingOnlyPattern()) | 806 pattern.isSelectorHidingOnlyPattern()) |
655 { | 807 { |
656 selectors.push(selector); | 808 if (!this.css.has(selector)) |
657 selectorFilters.push(pattern.text); | 809 { |
| 810 selectors.push(selector); |
| 811 selectorFilters.push(pattern.text); |
| 812 } |
658 } | 813 } |
659 else | 814 else |
660 { | 815 { |
661 for (let element of this.document.querySelectorAll(selector)) | 816 for (let element of this.document.querySelectorAll(selector)) |
662 { | 817 { |
663 elements.push(element); | 818 elements.push(element); |
664 elementFilters.push(pattern.text); | 819 elementFilters.push(pattern.text); |
665 } | 820 } |
666 } | 821 } |
667 } | 822 } |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
777 | 932 |
778 onLoad(event) | 933 onLoad(event) |
779 { | 934 { |
780 let stylesheet = event.target.sheet; | 935 let stylesheet = event.target.sheet; |
781 if (stylesheet) | 936 if (stylesheet) |
782 this.queueFiltering([stylesheet]); | 937 this.queueFiltering([stylesheet]); |
783 }, | 938 }, |
784 | 939 |
785 observe(mutations) | 940 observe(mutations) |
786 { | 941 { |
| 942 if (this.css.update(mutations)) |
| 943 this.addSelectorsFunc(this.css.selectors, this.css.filters); |
| 944 |
787 this.queueFiltering(null, mutations); | 945 this.queueFiltering(null, mutations); |
788 }, | 946 }, |
789 | 947 |
790 apply(patterns) | 948 apply(patterns) |
791 { | 949 { |
792 this.patterns = []; | 950 this.patterns = []; |
793 for (let pattern of patterns) | 951 for (let pattern of patterns) |
794 { | 952 { |
795 let selectors = this.parseSelector(pattern.selector); | 953 let selectors = this.parseSelector(pattern.selector); |
796 if (selectors != null && selectors.length > 0) | 954 if (selectors != null && selectors.length > 0) |
(...skipping 11 matching lines...) Expand all Loading... |
808 characterData: shouldObserveCharacterData(this.patterns), | 966 characterData: shouldObserveCharacterData(this.patterns), |
809 subtree: true | 967 subtree: true |
810 } | 968 } |
811 ); | 969 ); |
812 this.document.addEventListener("load", this.onLoad.bind(this), true); | 970 this.document.addEventListener("load", this.onLoad.bind(this), true); |
813 } | 971 } |
814 } | 972 } |
815 }; | 973 }; |
816 | 974 |
817 exports.ElemHideEmulation = ElemHideEmulation; | 975 exports.ElemHideEmulation = ElemHideEmulation; |
OLD | NEW |