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 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
237 | 237 |
238 const incompletePrefixRegexp = /[\s>+~]$/; | 238 const incompletePrefixRegexp = /[\s>+~]$/; |
239 | 239 |
240 function HasSelector(selectors) | 240 function HasSelector(selectors) |
241 { | 241 { |
242 this._innerSelectors = selectors; | 242 this._innerSelectors = selectors; |
243 } | 243 } |
244 | 244 |
245 HasSelector.prototype = { | 245 HasSelector.prototype = { |
246 requiresHiding: true, | 246 requiresHiding: true, |
| 247 dependsOnDOM: true, |
247 | 248 |
248 get dependsOnStyles() | 249 get dependsOnStyles() |
249 { | 250 { |
250 return this._innerSelectors.some(selector => selector.dependsOnStyles); | 251 return this._innerSelectors.some(selector => selector.dependsOnStyles); |
251 }, | 252 }, |
252 | 253 |
253 get dependsOnCharacterData() | 254 get dependsOnCharacterData() |
254 { | 255 { |
255 return this._innerSelectors.some( | 256 return this._innerSelectors.some( |
256 selector => selector.dependsOnCharacterData | 257 selector => selector.dependsOnCharacterData |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
299 } | 300 } |
300 }; | 301 }; |
301 | 302 |
302 function ContainsSelector(textContent) | 303 function ContainsSelector(textContent) |
303 { | 304 { |
304 this._regexp = makeRegExpParameter(textContent); | 305 this._regexp = makeRegExpParameter(textContent); |
305 } | 306 } |
306 | 307 |
307 ContainsSelector.prototype = { | 308 ContainsSelector.prototype = { |
308 requiresHiding: true, | 309 requiresHiding: true, |
| 310 dependsOnDOM: true, |
309 dependsOnCharacterData: true, | 311 dependsOnCharacterData: true, |
310 | 312 |
311 *getSelectors(prefix, subtree, styles) | 313 *getSelectors(prefix, subtree, styles) |
312 { | 314 { |
313 for (let element of this.getElements(prefix, subtree, styles)) | 315 for (let element of this.getElements(prefix, subtree, styles)) |
314 yield [makeSelector(element, ""), subtree]; | 316 yield [makeSelector(element, ""), subtree]; |
315 }, | 317 }, |
316 | 318 |
317 *getElements(prefix, subtree, styles) | 319 *getElements(prefix, subtree, styles) |
318 { | 320 { |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
376 yield [selector, subtree]; | 378 yield [selector, subtree]; |
377 } | 379 } |
378 }; | 380 }; |
379 | 381 |
380 function isSelectorHidingOnlyPattern(pattern) | 382 function isSelectorHidingOnlyPattern(pattern) |
381 { | 383 { |
382 return pattern.selectors.some(s => s.preferHideWithSelector) && | 384 return pattern.selectors.some(s => s.preferHideWithSelector) && |
383 !pattern.selectors.some(s => s.requiresHiding); | 385 !pattern.selectors.some(s => s.requiresHiding); |
384 } | 386 } |
385 | 387 |
| 388 function patternDependsOnStyles(pattern) |
| 389 { |
| 390 return pattern.selectors.some(s => s.dependsOnStyles); |
| 391 } |
| 392 |
| 393 function patternDependsOnDOM(pattern) |
| 394 { |
| 395 return pattern.selectors.some(s => s.dependsOnDOM); |
| 396 } |
| 397 |
| 398 function patternDependsOnStylesAndDOM(pattern) |
| 399 { |
| 400 return pattern.selectors.some(s => s.dependsOnStyles && s.dependsOnDOM); |
| 401 } |
| 402 |
| 403 function filterPatterns(patterns, {stylesheets, mutations}) |
| 404 { |
| 405 if (stylesheets && !mutations) |
| 406 return patterns.filter(patternDependsOnStyles); |
| 407 |
| 408 if (!stylesheets && mutations) |
| 409 return patterns.filter(patternDependsOnDOM); |
| 410 |
| 411 return patterns.slice(); |
| 412 } |
| 413 |
386 function shouldObserveAttributes(patterns) | 414 function shouldObserveAttributes(patterns) |
387 { | 415 { |
388 // Observe changes to attributes if either there's a plain selector that | 416 // Observe changes to attributes if either there's a plain selector that |
389 // looks like an ID selector, class selector, or attribute selector in one of | 417 // looks like an ID selector, class selector, or attribute selector in one of |
390 // the patterns (e.g. "a[href='https://example.com/']") | 418 // the patterns (e.g. "a[href='https://example.com/']") |
391 // or there's a properties selector nested inside a has selector | 419 // or there's a properties selector nested inside a has selector |
392 // (e.g. "div:-abp-has(:-abp-properties(color: blue))") | 420 // (e.g. "div:-abp-has(:-abp-properties(color: blue))") |
393 return patterns.some( | 421 return patterns.some( |
394 pattern => pattern.selectors.some( | 422 pattern => pattern.selectors.some( |
395 selector => selector.maybeDependsOnAttributes || | 423 selector => selector.maybeDependsOnAttributes || |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
495 return selectors; | 523 return selectors; |
496 }, | 524 }, |
497 | 525 |
498 /** | 526 /** |
499 * Processes the current document and applies all rules to it. | 527 * Processes the current document and applies all rules to it. |
500 * @param {CSSStyleSheet[]} [stylesheets] | 528 * @param {CSSStyleSheet[]} [stylesheets] |
501 * The list of new stylesheets that have been added to the document and | 529 * The list of new stylesheets that have been added to the document and |
502 * made reprocessing necessary. This parameter shouldn't be passed in for | 530 * made reprocessing necessary. This parameter shouldn't be passed in for |
503 * the initial processing, all of document's stylesheets will be considered | 531 * the initial processing, all of document's stylesheets will be considered |
504 * then and all rules, including the ones not dependent on styles. | 532 * then and all rules, including the ones not dependent on styles. |
| 533 * @param {MutationRecord[]} [mutations] |
| 534 * The list of DOM mutations that have been applied to the document and |
| 535 * made reprocessing necessary. This parameter shouldn't be passed in for |
| 536 * the initial processing, the entire document will be considered |
| 537 * then and all rules, including the ones not dependent on the DOM. |
505 * @param {function} [done] | 538 * @param {function} [done] |
506 * Callback to call when done. | 539 * Callback to call when done. |
507 */ | 540 */ |
508 _addSelectors(stylesheets, done) | 541 _addSelectors(stylesheets, mutations, done) |
509 { | 542 { |
| 543 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); |
| 544 |
510 let selectors = []; | 545 let selectors = []; |
511 let selectorFilters = []; | 546 let selectorFilters = []; |
512 | 547 |
513 let elements = []; | 548 let elements = []; |
514 let elementFilters = []; | 549 let elementFilters = []; |
515 | 550 |
516 let cssStyles = []; | 551 let cssStyles = []; |
517 | 552 |
518 let stylesheetOnlyChange = !!stylesheets; | 553 // If neither any style sheets nor any DOM mutations have been specified, |
519 if (!stylesheets) | 554 // do full processing. |
| 555 if (!stylesheets && !mutations) |
520 stylesheets = this.document.styleSheets; | 556 stylesheets = this.document.styleSheets; |
521 | 557 |
522 for (let stylesheet of stylesheets) | 558 // If there are any DOM mutations and any of the patterns depends on both |
| 559 // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the |
| 560 // rules in every style sheet in the document, because we need to run |
| 561 // querySelectorAll afterwards. On the other hand, if we only have patterns |
| 562 // that depend on either styles or DOM both not both |
| 563 // (e.g. -abp-properties or -abp-contains), we can skip this part. |
| 564 if (mutations && patterns.some(patternDependsOnStylesAndDOM)) |
| 565 stylesheets = this.document.styleSheets; |
| 566 |
| 567 for (let stylesheet of stylesheets || []) |
523 { | 568 { |
524 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 569 // Explicitly ignore third-party stylesheets to ensure consistent behavior |
525 // between Firefox and Chrome. | 570 // between Firefox and Chrome. |
526 if (!this.isSameOrigin(stylesheet)) | 571 if (!this.isSameOrigin(stylesheet)) |
527 continue; | 572 continue; |
528 | 573 |
529 let rules = stylesheet.cssRules; | 574 let rules = stylesheet.cssRules; |
530 if (!rules) | 575 if (!rules) |
531 continue; | 576 continue; |
532 | 577 |
533 for (let rule of rules) | 578 for (let rule of rules) |
534 { | 579 { |
535 if (rule.type != rule.STYLE_RULE) | 580 if (rule.type != rule.STYLE_RULE) |
536 continue; | 581 continue; |
537 | 582 |
538 cssStyles.push(stringifyStyle(rule)); | 583 cssStyles.push(stringifyStyle(rule)); |
539 } | 584 } |
540 } | 585 } |
541 | 586 |
542 let patterns = this.patterns.slice(); | |
543 let pattern = null; | 587 let pattern = null; |
544 let generator = null; | 588 let generator = null; |
545 | 589 |
546 let processPatterns = () => | 590 let processPatterns = () => |
547 { | 591 { |
548 let cycleStart = performance.now(); | 592 let cycleStart = performance.now(); |
549 | 593 |
550 if (!pattern) | 594 if (!pattern) |
551 { | 595 { |
552 if (!patterns.length) | 596 if (!patterns.length) |
553 { | 597 { |
554 this.addSelectorsFunc(selectors, selectorFilters); | 598 this.addSelectorsFunc(selectors, selectorFilters); |
555 this.hideElemsFunc(elements, elementFilters); | 599 this.hideElemsFunc(elements, elementFilters); |
556 if (typeof done == "function") | 600 if (typeof done == "function") |
557 done(); | 601 done(); |
558 return; | 602 return; |
559 } | 603 } |
560 | 604 |
561 pattern = patterns.shift(); | 605 pattern = patterns.shift(); |
562 | 606 |
563 if (stylesheetOnlyChange && | |
564 !pattern.selectors.some(selector => selector.dependsOnStyles)) | |
565 { | |
566 pattern = null; | |
567 return processPatterns(); | |
568 } | |
569 generator = evaluate(pattern.selectors, 0, "", | 607 generator = evaluate(pattern.selectors, 0, "", |
570 this.document, cssStyles); | 608 this.document, cssStyles); |
571 } | 609 } |
572 for (let selector of generator) | 610 for (let selector of generator) |
573 { | 611 { |
574 if (selector != null) | 612 if (selector != null) |
575 { | 613 { |
576 if (isSelectorHidingOnlyPattern(pattern)) | 614 if (isSelectorHidingOnlyPattern(pattern)) |
577 { | 615 { |
578 selectors.push(selector); | 616 selectors.push(selector); |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
613 }, | 651 }, |
614 | 652 |
615 _filteringInProgress: false, | 653 _filteringInProgress: false, |
616 _lastInvocation: -MIN_INVOCATION_INTERVAL, | 654 _lastInvocation: -MIN_INVOCATION_INTERVAL, |
617 _scheduledProcessing: null, | 655 _scheduledProcessing: null, |
618 | 656 |
619 /** | 657 /** |
620 * Re-run filtering either immediately or queued. | 658 * Re-run filtering either immediately or queued. |
621 * @param {CSSStyleSheet[]} [stylesheets] | 659 * @param {CSSStyleSheet[]} [stylesheets] |
622 * new stylesheets to be processed. This parameter should be omitted | 660 * new stylesheets to be processed. This parameter should be omitted |
623 * for DOM modification (full reprocessing required). | 661 * for full reprocessing. |
| 662 * @param {MutationRecord[]} [mutations] |
| 663 * new DOM mutations to be processed. This parameter should be omitted |
| 664 * for full reprocessing. |
624 */ | 665 */ |
625 queueFiltering(stylesheets) | 666 queueFiltering(stylesheets, mutations) |
626 { | 667 { |
627 let completion = () => | 668 let completion = () => |
628 { | 669 { |
629 this._lastInvocation = performance.now(); | 670 this._lastInvocation = performance.now(); |
630 this._filteringInProgress = false; | 671 this._filteringInProgress = false; |
631 if (this._scheduledProcessing) | 672 if (this._scheduledProcessing) |
632 { | 673 { |
633 let newStylesheets = this._scheduledProcessing.stylesheets; | 674 let {stylesheets, mutations} = this._scheduledProcessing; |
634 this._scheduledProcessing = null; | 675 this._scheduledProcessing = null; |
635 this.queueFiltering(newStylesheets); | 676 this.queueFiltering(stylesheets, mutations); |
636 } | 677 } |
637 }; | 678 }; |
638 | 679 |
639 if (this._scheduledProcessing) | 680 if (this._scheduledProcessing) |
640 { | 681 { |
641 if (!stylesheets) | 682 if (!stylesheets && !mutations) |
642 this._scheduledProcessing.stylesheets = null; | 683 { |
643 else if (this._scheduledProcessing.stylesheets) | 684 this._scheduledProcessing = {}; |
644 this._scheduledProcessing.stylesheets.push(...stylesheets); | 685 } |
| 686 else |
| 687 { |
| 688 if (stylesheets) |
| 689 { |
| 690 if (!this._scheduledProcessing.stylesheets) |
| 691 this._scheduledProcessing.stylesheets = []; |
| 692 this._scheduledProcessing.stylesheets.push(...stylesheets); |
| 693 } |
| 694 if (mutations) |
| 695 { |
| 696 if (!this._scheduledProcessing.mutations) |
| 697 this._scheduledProcessing.mutations = []; |
| 698 this._scheduledProcessing.mutations.push(...mutations); |
| 699 } |
| 700 } |
645 } | 701 } |
646 else if (this._filteringInProgress) | 702 else if (this._filteringInProgress) |
647 { | 703 { |
648 this._scheduledProcessing = {stylesheets}; | 704 this._scheduledProcessing = {stylesheets, mutations}; |
649 } | 705 } |
650 else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) | 706 else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) |
651 { | 707 { |
652 this._scheduledProcessing = {stylesheets}; | 708 this._scheduledProcessing = {stylesheets, mutations}; |
653 setTimeout(() => | 709 setTimeout(() => |
654 { | 710 { |
655 let newStylesheets = this._scheduledProcessing.stylesheets; | 711 let {stylesheets, mutations} = this._scheduledProcessing; |
656 this._filteringInProgress = true; | 712 this._filteringInProgress = true; |
657 this._scheduledProcessing = null; | 713 this._scheduledProcessing = null; |
658 this._addSelectors(newStylesheets, completion); | 714 this._addSelectors(stylesheets, mutations, completion); |
659 }, | 715 }, |
660 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); | 716 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); |
661 } | 717 } |
662 else if (this.document.readyState == "loading") | 718 else if (this.document.readyState == "loading") |
663 { | 719 { |
664 this._scheduledProcessing = {stylesheets}; | 720 this._scheduledProcessing = {stylesheets, mutations}; |
665 let handler = () => | 721 let handler = () => |
666 { | 722 { |
667 document.removeEventListener("DOMContentLoaded", handler); | 723 document.removeEventListener("DOMContentLoaded", handler); |
668 let newStylesheets = this._scheduledProcessing.stylesheets; | 724 let {stylesheets, mutations} = this._scheduledProcessing; |
669 this._filteringInProgress = true; | 725 this._filteringInProgress = true; |
670 this._scheduledProcessing = null; | 726 this._scheduledProcessing = null; |
671 this._addSelectors(newStylesheets, completion); | 727 this._addSelectors(stylesheets, mutations, completion); |
672 }; | 728 }; |
673 document.addEventListener("DOMContentLoaded", handler); | 729 document.addEventListener("DOMContentLoaded", handler); |
674 } | 730 } |
675 else | 731 else |
676 { | 732 { |
677 this._filteringInProgress = true; | 733 this._filteringInProgress = true; |
678 this._addSelectors(stylesheets, completion); | 734 this._addSelectors(stylesheets, mutations, completion); |
679 } | 735 } |
680 }, | 736 }, |
681 | 737 |
682 onLoad(event) | 738 onLoad(event) |
683 { | 739 { |
684 let stylesheet = event.target.sheet; | 740 let stylesheet = event.target.sheet; |
685 if (stylesheet) | 741 if (stylesheet) |
686 this.queueFiltering([stylesheet]); | 742 this.queueFiltering([stylesheet]); |
687 }, | 743 }, |
688 | 744 |
689 observe(mutations) | 745 observe(mutations) |
690 { | 746 { |
691 this.queueFiltering(); | 747 this.queueFiltering(null, mutations); |
692 }, | 748 }, |
693 | 749 |
694 apply(patterns) | 750 apply(patterns) |
695 { | 751 { |
696 this.patterns = []; | 752 this.patterns = []; |
697 for (let pattern of patterns) | 753 for (let pattern of patterns) |
698 { | 754 { |
699 let selectors = this.parseSelector(pattern.selector); | 755 let selectors = this.parseSelector(pattern.selector); |
700 if (selectors != null && selectors.length > 0) | 756 if (selectors != null && selectors.length > 0) |
701 this.patterns.push({selectors, text: pattern.text}); | 757 this.patterns.push({selectors, text: pattern.text}); |
(...skipping 10 matching lines...) Expand all Loading... |
712 characterData: shouldObserveCharacterData(this.patterns), | 768 characterData: shouldObserveCharacterData(this.patterns), |
713 subtree: true | 769 subtree: true |
714 } | 770 } |
715 ); | 771 ); |
716 this.document.addEventListener("load", this.onLoad.bind(this), true); | 772 this.document.addEventListener("load", this.onLoad.bind(this), true); |
717 } | 773 } |
718 } | 774 } |
719 }; | 775 }; |
720 | 776 |
721 exports.ElemHideEmulation = ElemHideEmulation; | 777 exports.ElemHideEmulation = ElemHideEmulation; |
OLD | NEW |