Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/content/elemHideEmulation.js

Issue 29728690: Issue 6504, 6458, 6446 - Update selectors based on DOM mutations (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Created March 21, 2018, 4:29 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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,
Manish Jethani 2018/03/21 04:45:44 Hopefully the inline comments are helpful.
478 selectorFilters, root)
479 {
480 let {target} = mutation;
481
482 // Existing selectors aren't affected by non-childList mutations.
483 if (mutation.type != "childList" || !root.contains(target))
484 return false;
485
486 // Find the mutation index, i.e. the index in the parent element where the
487 // mutation has occurred.
488 let index = 0;
489 let ref = mutation.previousSibling;
490 if (ref)
491 {
492 if (!(ref instanceof Element))
493 ref = ref.previousElementSibling;
494
495 if (ref)
496 index = indexOf(ref.parentNode.children, ref) + 1;
497 }
498
499 // Count the number of elements added and removed. Both can be non-zero in
500 // some cases, e.g. when Element.innerHTML is set on the parent element to
501 // overwrite all the existing child nodes.
502 let numAddedElements = 0;
503 let numRemovedElements = 0;
504
505 for (let node of mutation.addedNodes)
506 {
507 if (node instanceof Element)
508 numAddedElements++;
509 }
510
511 for (let node of mutation.removedNodes)
512 {
513 if (node instanceof Element)
514 numRemovedElements++;
515 }
516
517 let changed = false;
518
519 // Go through all the selectors and find any that match the mutation target.
520 let targetSelector = makeSelector(target) + " > ";
521 for (let i = 0; i < selectors.length; i++)
522 {
523 let selector = selectors[i];
524 if (selector.startsWith(targetSelector))
525 {
526 // If there's a match, extract the one-based child index.
527 let subSelector = selector.substring(targetSelector.length);
528 let match = /^[^:]*:nth-child\((\d+)\)/.exec(subSelector);
529 let nthIndex = match ? +match[1] : 0;
530
531 // If the child being targetted by the selector is before the mutation
532 // index (i.e. the one-based child index is not affected), do nothing.
533 if (nthIndex <= index)
534 continue;
535
536 nthIndex -= numRemovedElements;
537
538 // After adjusting for the number of removed elements, if the one-based
539 // child index appears to be lower than the mutation index, it means the
540 // child got removed. In that case we must delete the selector.
541 if (nthIndex <= index)
542 {
543 selectorSet.delete(selector);
544
545 selectors.splice(i, 1);
546 selectorFilters.splice(i, 1);
547 i--;
548
549 changed = true;
550
551 continue;
552 }
553
554 nthIndex += numAddedElements;
555
556 // If the one-based child index has changed, we must update the selector.
557 if (nthIndex != +match[1])
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
477 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) 572 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc)
478 { 573 {
479 this.document = document; 574 this.document = document;
480 this.addSelectorsFunc = addSelectorsFunc; 575 this.addSelectorsFunc = addSelectorsFunc;
481 this.hideElemsFunc = hideElemsFunc; 576 this.hideElemsFunc = hideElemsFunc;
482 this.observer = new MutationObserver(this.observe.bind(this)); 577 this.observer = new MutationObserver(this.observe.bind(this));
483 this.useInlineStyles = true; 578 this.useInlineStyles = true;
579 this.selectorSet = new Set();
Manish Jethani 2018/03/21 04:45:44 selectorSet here is mainly for faster lookups and
580 this.selectors = [];
581 this.selectorFilters = [];
484 } 582 }
485 583
486 ElemHideEmulation.prototype = { 584 ElemHideEmulation.prototype = {
487 isSameOrigin(stylesheet) 585 isSameOrigin(stylesheet)
488 { 586 {
489 try 587 try
490 { 588 {
491 return new URL(stylesheet.href).origin == this.document.location.origin; 589 return new URL(stylesheet.href).origin == this.document.location.origin;
492 } 590 }
493 catch (e) 591 catch (e)
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
572 * made reprocessing necessary. This parameter shouldn't be passed in for 670 * made reprocessing necessary. This parameter shouldn't be passed in for
573 * the initial processing, the entire document will be considered 671 * the initial processing, the entire document will be considered
574 * then and all rules, including the ones not dependent on the DOM. 672 * then and all rules, including the ones not dependent on the DOM.
575 * @param {function} [done] 673 * @param {function} [done]
576 * Callback to call when done. 674 * Callback to call when done.
577 */ 675 */
578 _addSelectors(stylesheets, mutations, done) 676 _addSelectors(stylesheets, mutations, done)
579 { 677 {
580 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); 678 let patterns = filterPatterns(this.patterns, {stylesheets, mutations});
581 679
680 if (patterns.length == 0)
Manish Jethani 2018/03/21 04:45:44 Minor optimization, definitely don't need to proce
681 return;
682
582 let selectors = []; 683 let selectors = [];
583 let selectorFilters = []; 684 let selectorFilters = [];
584 685
585 let elements = []; 686 let elements = [];
586 let elementFilters = []; 687 let elementFilters = [];
587 688
588 let cssStyles = []; 689 let cssStyles = [];
589 690
590 // If neither any style sheets nor any DOM mutations have been specified, 691 // If neither any style sheets nor any DOM mutations have been specified,
591 // do full processing. 692 // do full processing.
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
626 727
627 let processPatterns = () => 728 let processPatterns = () =>
628 { 729 {
629 let cycleStart = performance.now(); 730 let cycleStart = performance.now();
630 731
631 if (!pattern) 732 if (!pattern)
632 { 733 {
633 if (!patterns.length) 734 if (!patterns.length)
634 { 735 {
635 if (selectors.length > 0) 736 if (selectors.length > 0)
636 this.addSelectorsFunc(selectors, selectorFilters); 737 {
738 for (let selector of selectors)
739 this.selectorSet.add(selector);
740
741 this.selectors.push(...selectors);
742 this.selectorFilters.push(...selectorFilters);
743
744 this.addSelectorsFunc(this.selectors,
745 this.selectorFilters);
746 }
747
637 if (elements.length > 0) 748 if (elements.length > 0)
638 this.hideElemsFunc(elements, elementFilters); 749 this.hideElemsFunc(elements, elementFilters);
750
639 if (typeof done == "function") 751 if (typeof done == "function")
640 done(); 752 done();
641 return; 753 return;
642 } 754 }
643 755
644 pattern = patterns.shift(); 756 pattern = patterns.shift();
645 757
646 generator = evaluate(pattern.selectors, 0, "", 758 generator = evaluate(pattern.selectors, 0, "",
647 this.document, cssStyles); 759 this.document, cssStyles);
648 } 760 }
649 for (let selector of generator) 761 for (let selector of generator)
650 { 762 {
651 if (selector != null) 763 if (selector != null)
652 { 764 {
653 if (!this.useInlineStyles || 765 if (!this.useInlineStyles ||
654 pattern.isSelectorHidingOnlyPattern()) 766 pattern.isSelectorHidingOnlyPattern())
655 { 767 {
656 selectors.push(selector); 768 if (!this.selectorSet.has(selector))
Manish Jethani 2018/03/21 04:45:44 The reason we need this Set object is that we don'
657 selectorFilters.push(pattern.text); 769 {
770 selectors.push(selector);
771 selectorFilters.push(pattern.text);
772 }
658 } 773 }
659 else 774 else
660 { 775 {
661 for (let element of this.document.querySelectorAll(selector)) 776 for (let element of this.document.querySelectorAll(selector))
662 { 777 {
663 elements.push(element); 778 elements.push(element);
664 elementFilters.push(pattern.text); 779 elementFilters.push(pattern.text);
665 } 780 }
666 } 781 }
667 } 782 }
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
777 892
778 onLoad(event) 893 onLoad(event)
779 { 894 {
780 let stylesheet = event.target.sheet; 895 let stylesheet = event.target.sheet;
781 if (stylesheet) 896 if (stylesheet)
782 this.queueFiltering([stylesheet]); 897 this.queueFiltering([stylesheet]);
783 }, 898 },
784 899
785 observe(mutations) 900 observe(mutations)
786 { 901 {
902 let selectorsChanged = false;
903 for (let mutation of mutations)
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
787 this.queueFiltering(null, mutations); 915 this.queueFiltering(null, mutations);
788 }, 916 },
789 917
790 apply(patterns) 918 apply(patterns)
791 { 919 {
792 this.patterns = []; 920 this.patterns = [];
793 for (let pattern of patterns) 921 for (let pattern of patterns)
794 { 922 {
795 let selectors = this.parseSelector(pattern.selector); 923 let selectors = this.parseSelector(pattern.selector);
796 if (selectors != null && selectors.length > 0) 924 if (selectors != null && selectors.length > 0)
(...skipping 11 matching lines...) Expand all
808 characterData: shouldObserveCharacterData(this.patterns), 936 characterData: shouldObserveCharacterData(this.patterns),
809 subtree: true 937 subtree: true
810 } 938 }
811 ); 939 );
812 this.document.addEventListener("load", this.onLoad.bind(this), true); 940 this.document.addEventListener("load", this.onLoad.bind(this), true);
813 } 941 }
814 } 942 }
815 }; 943 };
816 944
817 exports.ElemHideEmulation = ElemHideEmulation; 945 exports.ElemHideEmulation = ElemHideEmulation;
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld