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-2015 Eyeo GmbH | 3 * Copyright (C) 2006-2015 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 |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
13 * | 13 * |
14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
16 */ | 16 */ |
17 | 17 |
18 /** | 18 /** |
19 * @fileOverview Definition of Filter class and its subclasses. | 19 * @fileOverview Definition of Filter class and its subclasses. |
20 */ | 20 */ |
21 | 21 |
22 let {FilterNotifier} = require("filterNotifier"); | 22 let {FilterNotifier} = require("filterNotifier"); |
| 23 let {Utils} = require("utils"); |
23 | 24 |
24 /** | 25 /** |
25 * Abstract base class for filters | 26 * Abstract base class for filters |
26 * | 27 * |
27 * @param {String} text string representation of the filter | 28 * @param {String} text string representation of the filter |
28 * @constructor | 29 * @constructor |
29 */ | 30 */ |
30 function Filter(text) | 31 function Filter(text) |
31 { | 32 { |
32 this.text = text; | 33 this.text = text; |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
78 /** | 79 /** |
79 * Regular expression that RegExp filters specified as RegExps should match | 80 * Regular expression that RegExp filters specified as RegExps should match |
80 * @type RegExp | 81 * @type RegExp |
81 */ | 82 */ |
82 Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[
^,\s]+)?)*)?$/; | 83 Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[
^,\s]+)?)*)?$/; |
83 /** | 84 /** |
84 * Regular expression that options on a RegExp filter should match | 85 * Regular expression that options on a RegExp filter should match |
85 * @type RegExp | 86 * @type RegExp |
86 */ | 87 */ |
87 Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/
; | 88 Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/
; |
| 89 /** |
| 90 * Regular expression that CSS property filters should match |
| 91 * Properties must not contain " or ' |
| 92 * @type RegExp |
| 93 */ |
| 94 Filter.csspropertyRegExp = /\[\-abp\-properties=(["'])([^"']+)\1\]/; |
88 | 95 |
89 /** | 96 /** |
90 * Creates a filter of correct type from its text representation - does the basi
c parsing and | 97 * Creates a filter of correct type from its text representation - does the basi
c parsing and |
91 * calls the right constructor then. | 98 * calls the right constructor then. |
92 * | 99 * |
93 * @param {String} text as in Filter() | 100 * @param {String} text as in Filter() |
94 * @return {Filter} | 101 * @return {Filter} |
95 */ | 102 */ |
96 Filter.fromText = function(text) | 103 Filter.fromText = function(text) |
97 { | 104 { |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
153 { | 160 { |
154 // Special treatment for element hiding filters, right side is allowed to co
ntain spaces | 161 // Special treatment for element hiding filters, right side is allowed to co
ntain spaces |
155 let [, domain, separator, selector] = /^(.*?)(#\@?#?)(.*)$/.exec(text); | 162 let [, domain, separator, selector] = /^(.*?)(#\@?#?)(.*)$/.exec(text); |
156 return domain.replace(/\s/g, "") + separator + selector.trim(); | 163 return domain.replace(/\s/g, "") + separator + selector.trim(); |
157 } | 164 } |
158 else | 165 else |
159 return text.replace(/\s/g, ""); | 166 return text.replace(/\s/g, ""); |
160 }; | 167 }; |
161 | 168 |
162 /** | 169 /** |
| 170 * Converts filter text into regular expression string |
| 171 * @param {String} text as in Filter() |
| 172 * @return {String} regular expression representation of filter text |
| 173 */ |
| 174 Filter.toRegExp = function(text) |
| 175 { |
| 176 return text |
| 177 .replace(/\*+/g, "*") // remove multiple wildcards |
| 178 .replace(/\^\|$/, "^") // remove anchors following separator placehold
er |
| 179 .replace(/\W/g, "\\$&") // escape special symbols |
| 180 .replace(/\\\*/g, ".*") // replace wildcards by .* |
| 181 // process separator placeholders (all ANSI characters but alphanumeric char
acters and _%.-) |
| 182 .replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x6
0\\x7B-\\x7F]|$)") |
| 183 .replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?") // process ex
tended anchor at expression start |
| 184 .replace(/^\\\|/, "^") // process anchor at expression start |
| 185 .replace(/\\\|$/, "$") // process anchor at expression end |
| 186 .replace(/^(\.\*)/, "") // remove leading wildcards |
| 187 .replace(/(\.\*)$/, ""); // remove trailing wildcards |
| 188 } |
| 189 |
| 190 /** |
163 * Class for invalid filters | 191 * Class for invalid filters |
164 * @param {String} text see Filter() | 192 * @param {String} text see Filter() |
165 * @param {String} reason Reason why this filter is invalid | 193 * @param {String} reason Reason why this filter is invalid |
166 * @constructor | 194 * @constructor |
167 * @augments Filter | 195 * @augments Filter |
168 */ | 196 */ |
169 function InvalidFilter(text, reason) | 197 function InvalidFilter(text, reason) |
170 { | 198 { |
171 Filter.call(this, text); | 199 Filter.call(this, text); |
172 | 200 |
(...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
536 * @type RegExp | 564 * @type RegExp |
537 */ | 565 */ |
538 get regexp() | 566 get regexp() |
539 { | 567 { |
540 // Despite this property being cached, the getter is called | 568 // Despite this property being cached, the getter is called |
541 // several times on Safari, due to WebKit bug 132872 | 569 // several times on Safari, due to WebKit bug 132872 |
542 let prop = Object.getOwnPropertyDescriptor(this, "regexp"); | 570 let prop = Object.getOwnPropertyDescriptor(this, "regexp"); |
543 if (prop) | 571 if (prop) |
544 return prop.value; | 572 return prop.value; |
545 | 573 |
546 // Remove multiple wildcards | 574 let source = Filter.toRegExp(this.regexpSource); |
547 let source = this.regexpSource | |
548 .replace(/\*+/g, "*") // remove multiple wildcards | |
549 .replace(/\^\|$/, "^") // remove anchors following separator placeho
lder | |
550 .replace(/\W/g, "\\$&") // escape special symbols | |
551 .replace(/\\\*/g, ".*") // replace wildcards by .* | |
552 // process separator placeholders (all ANSI characters but alphanumeric ch
aracters and _%.-) | |
553 .replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\
x60\\x7B-\\x7F]|$)") | |
554 .replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?") // process
extended anchor at expression start | |
555 .replace(/^\\\|/, "^") // process anchor at expression start | |
556 .replace(/\\\|$/, "$") // process anchor at expression end | |
557 .replace(/^(\.\*)/, "") // remove leading wildcards | |
558 .replace(/(\.\*)$/, ""); // remove trailing wildcards | |
559 | |
560 let regexp = new RegExp(source, this.matchCase ? "" : "i"); | 575 let regexp = new RegExp(source, this.matchCase ? "" : "i"); |
561 Object.defineProperty(this, "regexp", {value: regexp}); | 576 Object.defineProperty(this, "regexp", {value: regexp}); |
562 return regexp; | 577 return regexp; |
563 }, | 578 }, |
564 /** | 579 /** |
565 * Content types the filter applies to, combination of values from RegExpFilte
r.typeMap | 580 * Content types the filter applies to, combination of values from RegExpFilte
r.typeMap |
566 * @type Number | 581 * @type Number |
567 */ | 582 */ |
568 contentType: 0x7FFFFFFF, | 583 contentType: 0x7FFFFFFF, |
569 /** | 584 /** |
(...skipping 275 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
845 }; | 860 }; |
846 | 861 |
847 /** | 862 /** |
848 * Creates an element hiding filter from a pre-parsed text representation | 863 * Creates an element hiding filter from a pre-parsed text representation |
849 * | 864 * |
850 * @param {String} text same as in Filter() | 865 * @param {String} text same as in Filter() |
851 * @param {String} domain domain part of the text representation (can be emp
ty) | 866 * @param {String} domain domain part of the text representation (can be emp
ty) |
852 * @param {String} tagName tag name part (can be empty) | 867 * @param {String} tagName tag name part (can be empty) |
853 * @param {String} attrRules attribute matching rules (can be empty) | 868 * @param {String} attrRules attribute matching rules (can be empty) |
854 * @param {String} selector raw CSS selector (can be empty) | 869 * @param {String} selector raw CSS selector (can be empty) |
855 * @return {ElemHideFilter|ElemHideException|InvalidFilter} | 870 * @return {ElemHideFilter|ElemHideException|CSSPropertyFilter|InvalidFilter} |
856 */ | 871 */ |
857 ElemHideBase.fromText = function(text, domain, isException, tagName, attrRules,
selector) | 872 ElemHideBase.fromText = function(text, domain, isException, tagName, attrRules,
selector) |
858 { | 873 { |
859 if (!selector) | 874 if (!selector) |
860 { | 875 { |
861 if (tagName == "*") | 876 if (tagName == "*") |
862 tagName = ""; | 877 tagName = ""; |
863 | 878 |
864 let id = null; | 879 let id = null; |
865 let additional = ""; | 880 let additional = ""; |
866 if (attrRules) { | 881 if (attrRules) |
| 882 { |
867 attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g); | 883 attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g); |
868 for (let rule of attrRules) { | 884 for (let rule of attrRules) |
| 885 { |
869 rule = rule.substr(1, rule.length - 2); | 886 rule = rule.substr(1, rule.length - 2); |
870 let separatorPos = rule.indexOf("="); | 887 let separatorPos = rule.indexOf("="); |
871 if (separatorPos > 0) { | 888 if (separatorPos > 0) |
| 889 { |
872 rule = rule.replace(/=/, '="') + '"'; | 890 rule = rule.replace(/=/, '="') + '"'; |
873 additional += "[" + rule + "]"; | 891 additional += "[" + rule + "]"; |
874 } | 892 } |
875 else { | 893 else |
| 894 { |
876 if (id) | 895 if (id) |
877 { | |
878 let {Utils} = require("utils"); | |
879 return new InvalidFilter(text, Utils.getString("filter_elemhide_dupl
icate_id")); | 896 return new InvalidFilter(text, Utils.getString("filter_elemhide_dupl
icate_id")); |
880 } | 897 |
881 else | 898 id = rule; |
882 id = rule; | |
883 } | 899 } |
884 } | 900 } |
885 } | 901 } |
886 | 902 |
887 if (id) | 903 if (id) |
888 selector = tagName + "." + id + additional + "," + tagName + "#" + id + ad
ditional; | 904 selector = tagName + "." + id + additional + "," + tagName + "#" + id + ad
ditional; |
889 else if (tagName || additional) | 905 else if (tagName || additional) |
890 selector = tagName + additional; | 906 selector = tagName + additional; |
891 else | 907 else |
892 { | |
893 let {Utils} = require("utils"); | |
894 return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria
")); | 908 return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria
")); |
895 } | |
896 } | 909 } |
| 910 |
897 if (isException) | 911 if (isException) |
898 return new ElemHideException(text, domain, selector); | 912 return new ElemHideException(text, domain, selector); |
899 else | 913 |
900 return new ElemHideFilter(text, domain, selector); | 914 if (Filter.csspropertyRegExp.test(selector)) |
| 915 { |
| 916 // CSS property filters are inefficient so we need to make sure that |
| 917 // they're only applied if they specify active domains |
| 918 if (!/,[^~][^,.]*\.[^,]/.test("," + domain)) |
| 919 return new InvalidFilter(text, Utils.getString("filter_cssproperty_nodomai
n")); |
| 920 |
| 921 return new CSSPropertyFilter(text, domain, selector); |
| 922 } |
| 923 |
| 924 return new ElemHideFilter(text, domain, selector); |
901 }; | 925 }; |
902 | 926 |
903 /** | 927 /** |
904 * Class for element hiding filters | 928 * Class for element hiding filters |
905 * @param {String} text see Filter() | 929 * @param {String} text see Filter() |
906 * @param {String} domains see ElemHideBase() | 930 * @param {String} domains see ElemHideBase() |
907 * @param {String} selector see ElemHideBase() | 931 * @param {String} selector see ElemHideBase() |
908 * @constructor | 932 * @constructor |
909 * @augments ElemHideBase | 933 * @augments ElemHideBase |
910 */ | 934 */ |
(...skipping 19 matching lines...) Expand all Loading... |
930 function ElemHideException(text, domains, selector) | 954 function ElemHideException(text, domains, selector) |
931 { | 955 { |
932 ElemHideBase.call(this, text, domains, selector); | 956 ElemHideBase.call(this, text, domains, selector); |
933 } | 957 } |
934 exports.ElemHideException = ElemHideException; | 958 exports.ElemHideException = ElemHideException; |
935 | 959 |
936 ElemHideException.prototype = | 960 ElemHideException.prototype = |
937 { | 961 { |
938 __proto__: ElemHideBase.prototype | 962 __proto__: ElemHideBase.prototype |
939 }; | 963 }; |
| 964 |
| 965 /** |
| 966 * Class for CSS property filters |
| 967 * @param {String} text see Filter() |
| 968 * @param {String} domains see ElemHideBase() |
| 969 * @param {String} selector see ElemHideBase() |
| 970 * @constructor |
| 971 * @augments ElemHideBase |
| 972 */ |
| 973 function CSSPropertyFilter(text, domains, selector) |
| 974 { |
| 975 ElemHideBase.call(this, text, domains, selector); |
| 976 |
| 977 let properties; |
| 978 [properties, , this.regexpSource] = selector.match(Filter.csspropertyRegExp); |
| 979 [this.selectorPrefix, this.selectorSuffix] = selector.split(properties); |
| 980 } |
| 981 exports.CSSPropertyFilter = CSSPropertyFilter; |
| 982 |
| 983 CSSPropertyFilter.prototype = |
| 984 { |
| 985 __proto__: ElemHideBase.prototype, |
| 986 |
| 987 /** |
| 988 * Expression from which a regular expression should be generated for matching |
| 989 * CSS properties - for delayed creation of the regexpString property |
| 990 * @type String |
| 991 */ |
| 992 regexpSource: null, |
| 993 /** |
| 994 * Substring of CSS selector before properties for the HTML elements that |
| 995 * should be hidden |
| 996 * @type String |
| 997 */ |
| 998 selectorPrefix: null, |
| 999 /** |
| 1000 * Substring of CSS selector after properties for the HTML elements that |
| 1001 * should be hidden |
| 1002 * @type String |
| 1003 */ |
| 1004 selectorSuffix: null, |
| 1005 |
| 1006 /** |
| 1007 * Raw regular expression string to be used when testing CSS properties |
| 1008 * against this filter |
| 1009 * @type String |
| 1010 */ |
| 1011 get regexpString() |
| 1012 { |
| 1013 // Despite this property being cached, the getter is called |
| 1014 // several times on Safari, due to WebKit bug 132872 |
| 1015 let prop = Object.getOwnPropertyDescriptor(this, "regexpString"); |
| 1016 if (prop) |
| 1017 return prop.value; |
| 1018 |
| 1019 let regexp = Filter.toRegExp(this.regexpSource); |
| 1020 Object.defineProperty(this, "regexpString", {value: regexp}); |
| 1021 return regexp; |
| 1022 } |
| 1023 }; |
OLD | NEW |