|
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