Left: | ||
Right: |
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 |
(...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 updateSelectorsForMutation(mutation, selectorSet, selectors, | 477 function CSSSelectorManager(root) |
Manish Jethani
2018/03/21 04:45:44
Hopefully the inline comments are helpful.
| |
478 selectorFilters, root) | 478 { |
479 { | 479 this._root = root; |
480 let {target} = mutation; | 480 |
481 | 481 this.selectors = []; |
482 // Existing selectors aren't affected by non-childList mutations. | 482 this.filters = []; |
483 if (mutation.type != "childList" || !root.contains(target)) | 483 |
484 return false; | 484 this._lookup = new Set(); |
485 | 485 |
486 // Find the mutation index, i.e. the index in the parent element where the | 486 this._extended = false; |
487 // mutation has occurred. | 487 } |
488 let index = 0; | 488 |
489 let ref = mutation.previousSibling; | 489 CSSSelectorManager.prototype = { |
490 if (ref) | 490 add(selectors, filters) |
491 { | 491 { |
492 if (!(ref instanceof Element)) | 492 for (let i = 0; i < selectors.length; i++) |
493 ref = ref.previousElementSibling; | 493 { |
494 | 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; | |
495 if (ref) | 555 if (ref) |
496 index = indexOf(ref.parentNode.children, ref) + 1; | 556 { |
497 } | 557 if (!(ref instanceof Element)) |
498 | 558 ref = ref.previousElementSibling; |
499 // Count the number of elements added and removed. Both can be non-zero in | 559 |
500 // some cases, e.g. when Element.innerHTML is set on the parent element to | 560 if (ref) |
501 // overwrite all the existing child nodes. | 561 index = positionInParent(ref); |
502 let numAddedElements = 0; | 562 } |
503 let numRemovedElements = 0; | 563 |
504 | 564 // Count the number of elements added and removed. Both can be non-zero in |
505 for (let node of mutation.addedNodes) | 565 // some cases, e.g. when Element.innerHTML is set on the parent element to |
506 { | 566 // overwrite all the existing child nodes. |
507 if (node instanceof Element) | 567 let numAddedElements = 0; |
508 numAddedElements++; | 568 let numRemovedElements = 0; |
509 } | 569 |
510 | 570 for (let node of mutation.addedNodes) |
511 for (let node of mutation.removedNodes) | 571 { |
512 { | 572 if (node instanceof Element) |
513 if (node instanceof Element) | 573 numAddedElements++; |
514 numRemovedElements++; | 574 } |
515 } | 575 |
516 | 576 for (let node of mutation.removedNodes) |
517 let changed = false; | 577 { |
518 | 578 if (node instanceof Element) |
519 // Go through all the selectors and find any that match the mutation target. | 579 numRemovedElements++; |
520 let targetSelector = makeSelector(target) + " > "; | 580 } |
521 for (let i = 0; i < selectors.length; i++) | 581 |
522 { | 582 let changed = false; |
523 let selector = selectors[i]; | 583 |
524 if (selector.startsWith(targetSelector)) | 584 // Go through all the selectors and find any that match the mutation target. |
525 { | 585 let targetSelector = makeSelector(target) + " > "; |
526 // If there's a match, extract the one-based child index. | 586 for (let i = 0; i < this.selectors.length; i++) |
527 let subSelector = selector.substring(targetSelector.length); | 587 { |
528 let match = /^[^:]*:nth-child\((\d+)\)/.exec(subSelector); | 588 let selector = this.selectors[i]; |
529 let nthIndex = match ? +match[1] : 0; | 589 if (selector.startsWith(targetSelector)) |
530 | 590 { |
531 // If the child being targetted by the selector is before the mutation | 591 // If there's a match, extract the one-based child index. |
532 // index (i.e. the one-based child index is not affected), do nothing. | 592 let subSelector = selector.substring(targetSelector.length); |
533 if (nthIndex <= index) | 593 let match = /^[^:]*:nth-child\((\d+)\)/.exec(subSelector); |
534 continue; | 594 let nthIndex = match ? +match[1] : 0; |
535 | 595 |
536 nthIndex -= numRemovedElements; | 596 // If the child being targeted by the selector is before the mutation |
537 | 597 // index (i.e. the one-based child index is not affected), do nothing. |
538 // After adjusting for the number of removed elements, if the one-based | 598 if (nthIndex <= index) |
539 // child index appears to be lower than the mutation index, it means the | 599 continue; |
540 // child got removed. In that case we must delete the selector. | 600 |
541 if (nthIndex <= index) | 601 nthIndex -= numRemovedElements; |
542 { | 602 |
543 selectorSet.delete(selector); | 603 // After adjusting for the number of removed elements, if the one-based |
544 | 604 // child index appears to be lower than the mutation index, it means the |
545 selectors.splice(i, 1); | 605 // child got removed. In that case we must delete the selector. |
546 selectorFilters.splice(i, 1); | 606 if (nthIndex <= index) |
547 i--; | 607 { |
548 | 608 this.deleteAt(i--); |
549 changed = true; | 609 changed = true; |
550 | 610 continue; |
551 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 } | |
552 } | 623 } |
553 | 624 } |
554 nthIndex += numAddedElements; | 625 |
555 | 626 return changed; |
556 // If the one-based child index has changed, we must update the selector. | 627 } |
557 if (nthIndex != +match[1]) | 628 }; |
558 { | |
559 selectorSet.delete(selector); | |
560 selector = targetSelector + subSelector.replace(match[1], nthIndex); | |
561 selectorSet.add(selector); | |
562 selectors[i] = selector; | |
563 | |
564 changed = true; | |
565 } | |
566 } | |
567 } | |
568 | |
569 return changed; | |
570 } | |
571 | 629 |
572 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 630 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
573 { | 631 { |
574 this.document = document; | 632 this.document = document; |
575 this.addSelectorsFunc = addSelectorsFunc; | 633 this.addSelectorsFunc = addSelectorsFunc; |
576 this.hideElemsFunc = hideElemsFunc; | 634 this.hideElemsFunc = hideElemsFunc; |
577 this.observer = new MutationObserver(this.observe.bind(this)); | 635 this.observer = new MutationObserver(this.observe.bind(this)); |
578 this.useInlineStyles = true; | 636 this.useInlineStyles = true; |
579 this.selectorSet = new Set(); | 637 this.css = new CSSSelectorManager(this.document); |
Manish Jethani
2018/03/21 04:45:44
selectorSet here is mainly for faster lookups and
| |
580 this.selectors = []; | |
581 this.selectorFilters = []; | |
582 } | 638 } |
583 | 639 |
584 ElemHideEmulation.prototype = { | 640 ElemHideEmulation.prototype = { |
585 isSameOrigin(stylesheet) | 641 isSameOrigin(stylesheet) |
586 { | 642 { |
587 try | 643 try |
588 { | 644 { |
589 return new URL(stylesheet.href).origin == this.document.location.origin; | 645 return new URL(stylesheet.href).origin == this.document.location.origin; |
590 } | 646 } |
591 catch (e) | 647 catch (e) |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
670 * made reprocessing necessary. This parameter shouldn't be passed in for | 726 * made reprocessing necessary. This parameter shouldn't be passed in for |
671 * the initial processing, the entire document will be considered | 727 * the initial processing, the entire document will be considered |
672 * 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. |
673 * @param {function} [done] | 729 * @param {function} [done] |
674 * Callback to call when done. | 730 * Callback to call when done. |
675 */ | 731 */ |
676 _addSelectors(stylesheets, mutations, done) | 732 _addSelectors(stylesheets, mutations, done) |
677 { | 733 { |
678 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); | 734 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); |
679 | 735 |
680 if (patterns.length == 0) | 736 if (patterns.length == 0) |
Manish Jethani
2018/03/21 04:45:44
Minor optimization, definitely don't need to proce
| |
681 return; | 737 return; |
682 | 738 |
683 let selectors = []; | 739 let selectors = []; |
684 let selectorFilters = []; | 740 let selectorFilters = []; |
685 | 741 |
686 let elements = []; | 742 let elements = []; |
687 let elementFilters = []; | 743 let elementFilters = []; |
688 | 744 |
689 let cssStyles = []; | 745 let cssStyles = []; |
690 | 746 |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
728 let processPatterns = () => | 784 let processPatterns = () => |
729 { | 785 { |
730 let cycleStart = performance.now(); | 786 let cycleStart = performance.now(); |
731 | 787 |
732 if (!pattern) | 788 if (!pattern) |
733 { | 789 { |
734 if (!patterns.length) | 790 if (!patterns.length) |
735 { | 791 { |
736 if (selectors.length > 0) | 792 if (selectors.length > 0) |
737 { | 793 { |
738 for (let selector of selectors) | 794 this.css.add(selectors, selectorFilters); |
739 this.selectorSet.add(selector); | 795 this.addSelectorsFunc(this.css.selectors, this.css.filters); |
740 | |
741 this.selectors.push(...selectors); | |
742 this.selectorFilters.push(...selectorFilters); | |
743 | |
744 this.addSelectorsFunc(this.selectors, | |
745 this.selectorFilters); | |
746 } | 796 } |
747 | 797 |
748 if (elements.length > 0) | 798 if (elements.length > 0) |
749 this.hideElemsFunc(elements, elementFilters); | 799 this.hideElemsFunc(elements, elementFilters); |
750 | 800 |
751 if (typeof done == "function") | 801 if (typeof done == "function") |
752 done(); | 802 done(); |
753 return; | 803 return; |
754 } | 804 } |
755 | 805 |
756 pattern = patterns.shift(); | 806 pattern = patterns.shift(); |
757 | 807 |
758 generator = evaluate(pattern.selectors, 0, "", | 808 generator = evaluate(pattern.selectors, 0, "", |
759 this.document, cssStyles); | 809 this.document, cssStyles); |
760 } | 810 } |
761 for (let selector of generator) | 811 for (let selector of generator) |
762 { | 812 { |
763 if (selector != null) | 813 if (selector != null) |
764 { | 814 { |
765 if (!this.useInlineStyles || | 815 if (!this.useInlineStyles || |
766 pattern.isSelectorHidingOnlyPattern()) | 816 pattern.isSelectorHidingOnlyPattern()) |
767 { | 817 { |
768 if (!this.selectorSet.has(selector)) | 818 if (!this.css.has(selector)) |
Manish Jethani
2018/03/21 04:45:44
The reason we need this Set object is that we don'
| |
769 { | 819 { |
770 selectors.push(selector); | 820 selectors.push(selector); |
771 selectorFilters.push(pattern.text); | 821 selectorFilters.push(pattern.text); |
772 } | 822 } |
773 } | 823 } |
774 else | 824 else |
775 { | 825 { |
776 for (let element of this.document.querySelectorAll(selector)) | 826 for (let element of this.document.querySelectorAll(selector)) |
777 { | 827 { |
778 elements.push(element); | 828 elements.push(element); |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
892 | 942 |
893 onLoad(event) | 943 onLoad(event) |
894 { | 944 { |
895 let stylesheet = event.target.sheet; | 945 let stylesheet = event.target.sheet; |
896 if (stylesheet) | 946 if (stylesheet) |
897 this.queueFiltering([stylesheet]); | 947 this.queueFiltering([stylesheet]); |
898 }, | 948 }, |
899 | 949 |
900 observe(mutations) | 950 observe(mutations) |
901 { | 951 { |
902 let selectorsChanged = false; | 952 if (this.css.update(mutations)) |
903 for (let mutation of 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
| |
904 { | |
905 if (updateSelectorsForMutation(mutation, this.selectorSet, this.selectors, | |
906 this.selectorFilters, this.document)) | |
907 { | |
908 selectorsChanged = true; | |
909 } | |
910 } | |
911 | |
912 if (selectorsChanged) | |
Manish Jethani
2018/03/21 04:45:44
Call addSelectorsFunc immediately so the UI is upd
| |
913 this.addSelectorsFunc(this.selectors, this.selectorFilters); | |
914 | 954 |
915 this.queueFiltering(null, mutations); | 955 this.queueFiltering(null, mutations); |
916 }, | 956 }, |
917 | 957 |
918 apply(patterns) | 958 apply(patterns) |
919 { | 959 { |
920 this.patterns = []; | 960 this.patterns = []; |
921 for (let pattern of patterns) | 961 for (let pattern of patterns) |
922 { | 962 { |
923 let selectors = this.parseSelector(pattern.selector); | 963 let selectors = this.parseSelector(pattern.selector); |
(...skipping 12 matching lines...) Expand all Loading... | |
936 characterData: shouldObserveCharacterData(this.patterns), | 976 characterData: shouldObserveCharacterData(this.patterns), |
937 subtree: true | 977 subtree: true |
938 } | 978 } |
939 ); | 979 ); |
940 this.document.addEventListener("load", this.onLoad.bind(this), true); | 980 this.document.addEventListener("load", this.onLoad.bind(this), true); |
941 } | 981 } |
942 } | 982 } |
943 }; | 983 }; |
944 | 984 |
945 exports.ElemHideEmulation = ElemHideEmulation; | 985 exports.ElemHideEmulation = ElemHideEmulation; |
LEFT | RIGHT |