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: Encapsulate selector management in CSSSelectorManager class Created March 21, 2018, 2:32 p.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 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
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
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
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
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;
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