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

Delta Between Two Patch Sets: lib/content/elemHideEmulation.js

Issue 29728690: Issue 6504, 6458, 6446 - Update selectors based on DOM mutations (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Created March 21, 2018, 4:29 a.m.
Right Patch Set: Add special case check for documents with no extended selectors Created March 21, 2018, 3:28 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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, 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
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
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
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
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;
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld