|
93 | 93 | [ ':contains', ["(", ")", [1, -1], ":-abp-contains"] ] |
94 | 94 | ]); |
95 | 95 | this.extended = false; |
| 96 | + this.snippet = false; |
96 | 97 | return this; |
97 | 98 | }; |
98 | 99 |
|
|
125 | 126 | } |
126 | 127 | return str; |
127 | 128 | } |
| 129 | + FilterParser.prototype.convertuBOStyleToABP = function(suffix, spos) { |
| 130 | + let stylePrefix = suffix.slice(0, spos); |
| 131 | + let styleSuffix = suffix.slice(spos); |
| 132 | + let style = /:style\(([^)]+)\)/.exec(styleSuffix)[1]; |
| 133 | + return `${stylePrefix} {${style}}`; |
| 134 | + } |
| 135 | + FilterParser.prototype.convertuBOJsToABP = function(suffix) { |
| 136 | + let invalid = false; |
| 137 | + let arrParams = /^\+js\(([^)]+)\)/.exec(suffix)[1].replace(/,[\s]+/gi," ").replace(".js","").split(" "); |
| 138 | + let snippetName = arrParams[0]; |
| 139 | + if(!supportedSnippet.has(snippetName)) { |
| 140 | + invalid = true; |
| 141 | + } |
| 142 | + return [invalid, arrParams.join(" ")]; |
| 143 | + } |
128 | 144 | FilterParser.prototype.parse = function(s) { |
129 | 145 | // important! |
130 | 146 | this.reset(); |
|
154 | 170 | if ( this.suffix.charAt(1) === '[' && this.suffix.slice(2, 9) === 'href^="' ) { |
155 | 171 | this.suffix = this.suffix.slice(1); |
156 | 172 | } |
| 173 | + |
157 | 174 | this.type = matches[2].charAt(1); |
158 | 175 | if(reAdguardExtCssSyntax.test(this.suffix)) { |
159 | 176 | this.suffix = this.convertAdGuardRule(this.suffix); |
160 | 177 | } |
161 | | - |
| 178 | + let spos = this.suffix.indexOf(":style"); |
| 179 | + if(spos !== -1) { |
| 180 | + this.suffix = this.convertuBOStyleToABP(this.suffix, spos); |
| 181 | + this.type = '$'; |
| 182 | + } |
| 183 | + if(/^\+js\(/.test(this.suffix)) { |
| 184 | + [this.invalid, this.suffix] = this.convertuBOJsToABP(this.suffix); |
| 185 | + if(this.invalid) return this; |
| 186 | + this.type = '$'; |
| 187 | + } |
162 | 188 | if(this.type == "$") { |
163 | 189 | if(reAdguardCssSyntax.test(this.suffix)) { |
164 | | - this.extended = false; |
| 190 | + if(reprocSelector.test(this.suffix)) |
| 191 | + this.extended = true; |
165 | 192 | } else { |
166 | 193 | let m = /([^\s]+)/.exec(this.suffix); |
167 | 194 | if(!supportedSnippet.has(m[0])) { |
168 | 195 | this.invalid = true; |
169 | 196 | return this; |
170 | 197 | } |
171 | | - this.extended = true; |
| 198 | + this.snippet = true; |
172 | 199 | } |
173 | 200 | } |
174 | 201 | this.unhide = this.type === '@' ? 1 : 0; |
|
677 | 704 | return {text: content.substring(startIndex, i), end: i}; |
678 | 705 | } |
679 | 706 |
|
680 | | - const normalizedOperators = new Set(['-abp-contains', '-abp-has', '-abp-properties' , 'matches-css', 'matches-css-after', 'matches-css-before']); |
| 707 | + const normalizedOperators = new Set(['-abp-contains', '-abp-has', '-abp-properties' , 'matches-css', 'matches-css-after', 'matches-css-before', 'not']); |
681 | 708 | const shortNames = new Map([ |
682 | 709 | ["pseudoCls", "ps"], |
683 | 710 | ["prefix", "pf"], |
684 | 711 | ["selectorText", "st"], |
685 | 712 | ["_innerSelectors", "_is"], |
686 | | - ["maybeContainsSiblingCombinators", "csc"], |
| 713 | + ["startsWithSiblingOperator", "sso"], |
687 | 714 | ["hasParallelSiblingSelector", "pss"] |
688 | 715 | ]); |
689 | 716 |
|
690 | 717 | FilterContainer.prototype.parseProcedure = function(expression) { |
691 | 718 | let tasks = [], prefix, remaining, parsed, selectorText, isValid = true, procSelector = null, pseudoClass = null; |
692 | 719 | let matches = pseudoClassReg.exec(expression); |
693 | 720 | if(!matches) { |
| 721 | + this.shouldObserveAttributes = !this.shouldObserveAttributes ? /[#.]|\[.+\]/.test(expression) : true; |
694 | 722 | return [true, |
695 | 723 | [{ |
696 | 724 | ["plain"]: { |
697 | 725 | [shortNames.get('pseudoCls')]: null, |
698 | 726 | [shortNames.get('prefix')]: "", |
699 | 727 | [shortNames.get('selectorText')]: expression, |
700 | 728 | [shortNames.get('_innerSelectors')]: null, |
701 | | - [shortNames.get('maybeContainsSiblingCombinators')]: /[~+]/.test(expression), |
| 729 | + [shortNames.get('startsWithSiblingOperator')]: /^\s*[+~]/.test(expression), |
702 | 730 | [shortNames.get('hasParallelSiblingSelector')]: false |
703 | 731 | } |
704 | 732 | }] |
|
713 | 741 | selectorText = parsed.text; |
714 | 742 | pseudoClass = (matches[3] == "matches-css-after" ? ":after" : (matches[3] == "matches-css-before" ? ":before" : null )); |
715 | 743 |
|
716 | | - if(matches[3] == "-abp-has") { |
| 744 | + if(matches[3] == "-abp-contains") |
| 745 | + this.shouldObserveCharacterData = true; |
| 746 | + |
| 747 | + this.shouldObserveAttributes = !this.shouldObserveAttributes ? /[#.]|\[.+\]/.test(prefix) : true; |
| 748 | + |
| 749 | + if(matches[3] == "-abp-has" || matches[3] == "not") { |
717 | 750 | [isValid, procSelector] = this.parseProcedure(selectorText); |
718 | 751 | } else if(normalizedOperators.has(matches[3])) { |
719 | 752 | isValid = true; |
|
728 | 761 | [shortNames.get('prefix')]: prefix, |
729 | 762 | [shortNames.get('selectorText')]: procSelector === null ? selectorText : null, |
730 | 763 | [shortNames.get('_innerSelectors')]: procSelector, |
| 764 | + [shortNames.get('startsWithSiblingOperator')]: /^\s*[+~]/.test(prefix), |
731 | 765 | [shortNames.get('hasParallelSiblingSelector')]: false |
732 | 766 | } |
733 | 767 | }); |
|
736 | 770 | let suffix; |
737 | 771 | [isValid, suffix] = this.parseProcedure(suffixtext); |
738 | 772 | if(isValid) { |
739 | | - if(Object.keys(suffix).length == 1) { |
740 | | - if(Object.keys(suffix)[0] == "plain" && suffix["plain"].maybeContainsSiblingCombinators) { |
| 773 | + if(suffix.length > 0) { |
| 774 | + if(Object.values(suffix[0])[0][shortNames.get('startsWithSiblingOperator')]) { |
741 | 775 | for (let task of tasks) { |
742 | | - if(Object.keys(task)[0] == "-abp-has" || Object.keys(task)[0] == "-abp-contains" || Object.keys(task)[0] == "-abp-properties") { |
743 | | - task[Object.keys(task)[0]].hasParallelSiblingSelector = true; |
| 776 | + if(Object.keys(task)[0] != "plain") { |
| 777 | + task[Object.keys(task)[0]][shortNames.get('hasParallelSiblingSelector')] = true; |
744 | 778 | } |
745 | 779 | } |
746 | 780 | } |
|
772 | 806 |
|
773 | 807 | let isValid; |
774 | 808 | this.pseudoClassExpression = false; |
775 | | - if(this.parser.type == "$" && this.parser.extended) { |
776 | | - isValid = true; |
777 | | - } else if(this.parser.type == "$" && !this.parser.extended) { |
778 | | - let matches = /(.+?)\s*\{(.*)\}\s*$/.exec(parsed.suffix); |
779 | | - isValid = this.isValidSelector(matches[1].trim()) && isValidStyle(matches[2].trim()); |
| 809 | + this.shouldObserveAttributes = false; |
| 810 | + this.shouldObserveCharacterData = false; |
| 811 | + |
| 812 | + if(this.parser.type == "$") { |
| 813 | + if(this.parser.snippet) |
| 814 | + isValid = true; |
| 815 | + else if(this.parser.extended) { |
| 816 | + let matches = /(.+?)\s*\{(.*)\}\s*$/.exec(parsed.suffix); |
| 817 | + isValid = this.isValidSelector(matches[1].trim()); |
| 818 | + if(!isValid && reprocSelector.test(matches[1].trim()) && isValidStyle(matches[2].trim())) { |
| 819 | + let tasks; |
| 820 | + [isValid, tasks] = this.parseProcedure(matches[1].trim()); |
| 821 | + this.pseudoClassExpression = true; |
| 822 | + parsed.suffix = JSON.stringify({'tasks': tasks, 'style': matches[2].trim(), 'attr': this.shouldObserveAttributes, 'data': this.shouldObserveCharacterData}); |
| 823 | + } |
| 824 | + } else { |
| 825 | + let matches = /(.+?)\s*\{(.*)\}\s*$/.exec(parsed.suffix); |
| 826 | + isValid = this.isValidSelector(matches[1].trim()) && isValidStyle(matches[2].trim()); |
| 827 | + } |
780 | 828 | } |
781 | 829 | else { |
782 | 830 | isValid = this.isValidSelector(parsed.suffix); |
783 | 831 | if(!isValid && reprocSelector.test(parsed.suffix)) { |
784 | 832 | let tasks; |
785 | 833 | [isValid, tasks] = this.parseProcedure(parsed.suffix); |
786 | 834 | this.pseudoClassExpression = true; |
787 | | - parsed.suffix = JSON.stringify(tasks); |
| 835 | + parsed.suffix = JSON.stringify({'tasks': tasks, 'style': "", 'attr': this.shouldObserveAttributes, 'data': this.shouldObserveCharacterData}); |
788 | 836 | } |
789 | 837 | } |
790 | 838 | // For hostname- or entity-based filters, class- or id-based selectors are |
|
911 | 959 | hash = this.pseudoClassExpression ? makeHash(unhide, domain, this.domainHashMask, this.procedureMask) : makeHash(unhide, domain, this.domainHashMask); |
912 | 960 | } |
913 | 961 | let hshash = µb.tokenHash(hostname); |
914 | | - if(parsed.type == "$" && parsed.extended) |
| 962 | + if(parsed.type == "$" && parsed.snippet) |
915 | 963 | out.push(['hs+', hash, hshash, parsed.suffix]); |
916 | | - else if(parsed.type == "$" && !parsed.extended) |
| 964 | + else if(parsed.type == "$") |
917 | 965 | out.push(['hs', hash, hshash, parsed.suffix]); |
918 | 966 | else |
919 | 967 | out.push(['h', hash, hshash, parsed.suffix]); |
|
923 | 971 |
|
924 | 972 | FilterContainer.prototype.compileEntitySelector = function(hostname, parsed, out) { |
925 | 973 | let entity = hostname.slice(0, -2); |
926 | | - if(parsed.type == "$" && parsed.extended) |
| 974 | + if(parsed.type == "$" && parsed.snippet) |
927 | 975 | out.push(['es+', entity, parsed.suffix]); |
928 | | - else if(parsed.type == "$" && !parsed.extended) |
| 976 | + else if(parsed.type == "$") |
929 | 977 | out.push(['es', entity, parsed.suffix]); |
930 | 978 | else |
931 | 979 | out.push(['e', entity, parsed.suffix]); |
|
1359 | 1407 | cosmeticDonthide: [], |
1360 | 1408 | netHide: [], |
1361 | 1409 | netCollapse: µb.userSettings.collapseBlocked, |
1362 | | - cosmeticUserCss: [] |
| 1410 | + cosmeticUserCss: [], |
| 1411 | + shouldObserveAttributes: false, |
| 1412 | + shouldObserveCharacterData: false |
1363 | 1413 | }; |
1364 | | - if(options.skipCosmeticFiltering){ |
| 1414 | + if(options.skipCosmeticFiltering) { |
1365 | 1415 | return r; |
1366 | 1416 | } |
1367 | 1417 |
|
|
1401 | 1451 | if(this.hostnameFilterDataView.hasOwnProperty("style")) { |
1402 | 1452 | hash = makeHash(0, domain, this.domainHashMask); |
1403 | 1453 | this.hostnameFilterDataView["style"].retrieve(hosthashes, hash, r.cosmeticUserCss); |
| 1454 | + |
| 1455 | + hash = makeHash(0, domain, this.domainHashMask, this.procedureMask); |
| 1456 | + this.hostnameFilterDataView["style"].retrieve(hosthashes, hash, r.procedureHide); |
1404 | 1457 | } |
1405 | 1458 |
|
1406 | 1459 | // No entity exceptions as of now |
|
1410 | 1463 | hash = makeHash(0, domain, this.domainHashMask, this.procedureMask); |
1411 | 1464 | this.hostnameFilterDataView[flag].retrieve(hosthashes, hash, r.procedureHide); |
1412 | 1465 |
|
| 1466 | + if(r.procedureHide.length > 0) { |
| 1467 | + r.shouldObserveAttributes = r.procedureHide.some(selector => selector.indexOf("\"attr\":true") !== -1); |
| 1468 | + r.shouldObserveCharacterData = r.procedureHide.some(selector => selector.indexOf("\"data\":true") !== -1); |
| 1469 | + } |
| 1470 | + |
1413 | 1471 | if(request.procedureSelectorsOnly) { |
1414 | | - return r.procedureHide; |
| 1472 | + return { |
| 1473 | + procedureHide :r.procedureHide, |
| 1474 | + shouldObserveAttributes: r.shouldObserveAttributes, |
| 1475 | + shouldObserveCharacterData: r.shouldObserveCharacterData |
| 1476 | + }; |
1415 | 1477 | } |
1416 | 1478 |
|
1417 | 1479 | // https://github.com/uBlockAdmin/uBlock/issues/188 |
|
0 commit comments