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

Side by Side Diff: lib/filterClasses.js

Issue 29335650: Issue 2595 - Use the core code from adblockpluscore (Closed)
Patch Set: Created Feb. 4, 2016, 6:35 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/elemHide.js ('k') | lib/filterListener.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH
4 *
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
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
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/>.
16 */
17
18 /**
19 * @fileOverview Definition of Filter class and its subclasses.
20 */
21
22 let {FilterNotifier} = require("filterNotifier");
23 let {Utils} = require("utils");
24
25 /**
26 * Abstract base class for filters
27 *
28 * @param {String} text string representation of the filter
29 * @constructor
30 */
31 function Filter(text)
32 {
33 this.text = text;
34 this.subscriptions = [];
35 }
36 exports.Filter = Filter;
37
38 Filter.prototype =
39 {
40 /**
41 * String representation of the filter
42 * @type String
43 */
44 text: null,
45
46 /**
47 * Filter subscriptions the filter belongs to
48 * @type Subscription[]
49 */
50 subscriptions: null,
51
52 /**
53 * Filter type as a string, e.g. "blocking".
54 * @type String
55 */
56 get type()
57 {
58 throw new Error("Please define filter type in the subclass");
59 },
60
61 /**
62 * Serializes the filter to an array of strings for writing out on the disk.
63 * @param {string[]} buffer buffer to push the serialization results into
64 */
65 serialize: function(buffer)
66 {
67 buffer.push("[Filter]");
68 buffer.push("text=" + this.text);
69 },
70
71 toString: function()
72 {
73 return this.text;
74 }
75 };
76
77 /**
78 * Cache for known filters, maps string representation to filter objects.
79 * @type Object
80 */
81 Filter.knownFilters = Object.create(null);
82
83 /**
84 * Regular expression that element hiding filters should match
85 * @type RegExp
86 */
87 Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(\@)?(?:([\w\-]+|\*)((?:\([\w\-]+(?: [$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/;
88 /**
89 * Regular expression that RegExp filters specified as RegExps should match
90 * @type RegExp
91 */
92 Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[ ^,\s]+)?)*)?$/;
93 /**
94 * Regular expression that options on a RegExp filter should match
95 * @type RegExp
96 */
97 Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/ ;
98 /**
99 * Regular expression that CSS property filters should match
100 * Properties must not contain " or '
101 * @type RegExp
102 */
103 Filter.csspropertyRegExp = /\[\-abp\-properties=(["'])([^"']+)\1\]/;
104
105 /**
106 * Creates a filter of correct type from its text representation - does the basi c parsing and
107 * calls the right constructor then.
108 *
109 * @param {String} text as in Filter()
110 * @return {Filter}
111 */
112 Filter.fromText = function(text)
113 {
114 if (text in Filter.knownFilters)
115 return Filter.knownFilters[text];
116
117 let ret;
118 let match = (text.indexOf("#") >= 0 ? Filter.elemhideRegExp.exec(text) : null) ;
119 if (match)
120 ret = ElemHideBase.fromText(text, match[1], !!match[2], match[3], match[4], match[5]);
121 else if (text[0] == "!")
122 ret = new CommentFilter(text);
123 else
124 ret = RegExpFilter.fromText(text);
125
126 Filter.knownFilters[ret.text] = ret;
127 return ret;
128 };
129
130 /**
131 * Deserializes a filter
132 *
133 * @param {Object} obj map of serialized properties and their values
134 * @return {Filter} filter or null if the filter couldn't be created
135 */
136 Filter.fromObject = function(obj)
137 {
138 let ret = Filter.fromText(obj.text);
139 if (ret instanceof ActiveFilter)
140 {
141 if ("disabled" in obj)
142 ret._disabled = (obj.disabled == "true");
143 if ("hitCount" in obj)
144 ret._hitCount = parseInt(obj.hitCount) || 0;
145 if ("lastHit" in obj)
146 ret._lastHit = parseInt(obj.lastHit) || 0;
147 }
148 return ret;
149 };
150
151 /**
152 * Removes unnecessary whitespaces from filter text, will only return null if
153 * the input parameter is null.
154 */
155 Filter.normalize = function(/**String*/ text) /**String*/
156 {
157 if (!text)
158 return text;
159
160 // Remove line breaks and such
161 text = text.replace(/[^\S ]/g, "");
162
163 if (/^\s*!/.test(text))
164 {
165 // Don't remove spaces inside comments
166 return text.trim();
167 }
168 else if (Filter.elemhideRegExp.test(text))
169 {
170 // Special treatment for element hiding filters, right side is allowed to co ntain spaces
171 let [, domain, separator, selector] = /^(.*?)(#\@?#?)(.*)$/.exec(text);
172 return domain.replace(/\s/g, "") + separator + selector.trim();
173 }
174 else
175 return text.replace(/\s/g, "");
176 };
177
178 /**
179 * Converts filter text into regular expression string
180 * @param {String} text as in Filter()
181 * @return {String} regular expression representation of filter text
182 */
183 Filter.toRegExp = function(text)
184 {
185 return text
186 .replace(/\*+/g, "*") // remove multiple wildcards
187 .replace(/\^\|$/, "^") // remove anchors following separator placehold er
188 .replace(/\W/g, "\\$&") // escape special symbols
189 .replace(/\\\*/g, ".*") // replace wildcards by .*
190 // process separator placeholders (all ANSI characters but alphanumeric char acters and _%.-)
191 .replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x6 0\\x7B-\\x7F]|$)")
192 .replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?") // process ex tended anchor at expression start
193 .replace(/^\\\|/, "^") // process anchor at expression start
194 .replace(/\\\|$/, "$") // process anchor at expression end
195 .replace(/^(\.\*)/, "") // remove leading wildcards
196 .replace(/(\.\*)$/, ""); // remove trailing wildcards
197 }
198
199 /**
200 * Class for invalid filters
201 * @param {String} text see Filter()
202 * @param {String} reason Reason why this filter is invalid
203 * @constructor
204 * @augments Filter
205 */
206 function InvalidFilter(text, reason)
207 {
208 Filter.call(this, text);
209
210 this.reason = reason;
211 }
212 exports.InvalidFilter = InvalidFilter;
213
214 InvalidFilter.prototype =
215 {
216 __proto__: Filter.prototype,
217
218 type: "invalid",
219
220 /**
221 * Reason why this filter is invalid
222 * @type String
223 */
224 reason: null,
225
226 /**
227 * See Filter.serialize()
228 */
229 serialize: function(buffer) {}
230 };
231
232 /**
233 * Class for comments
234 * @param {String} text see Filter()
235 * @constructor
236 * @augments Filter
237 */
238 function CommentFilter(text)
239 {
240 Filter.call(this, text);
241 }
242 exports.CommentFilter = CommentFilter;
243
244 CommentFilter.prototype =
245 {
246 __proto__: Filter.prototype,
247
248 type: "comment",
249
250 /**
251 * See Filter.serialize()
252 */
253 serialize: function(buffer) {}
254 };
255
256 /**
257 * Abstract base class for filters that can get hits
258 * @param {String} text see Filter()
259 * @param {String} [domains] Domains that the filter is restricted to separated by domainSeparator e.g. "foo.com|bar.com|~baz.com"
260 * @constructor
261 * @augments Filter
262 */
263 function ActiveFilter(text, domains)
264 {
265 Filter.call(this, text);
266
267 this.domainSource = domains;
268 }
269 exports.ActiveFilter = ActiveFilter;
270
271 ActiveFilter.prototype =
272 {
273 __proto__: Filter.prototype,
274
275 _disabled: false,
276 _hitCount: 0,
277 _lastHit: 0,
278
279 /**
280 * Defines whether the filter is disabled
281 * @type Boolean
282 */
283 get disabled()
284 {
285 return this._disabled;
286 },
287 set disabled(value)
288 {
289 if (value != this._disabled)
290 {
291 let oldValue = this._disabled;
292 this._disabled = value;
293 FilterNotifier.triggerListeners("filter.disabled", this, value, oldValue);
294 }
295 return this._disabled;
296 },
297
298 /**
299 * Number of hits on the filter since the last reset
300 * @type Number
301 */
302 get hitCount()
303 {
304 return this._hitCount;
305 },
306 set hitCount(value)
307 {
308 if (value != this._hitCount)
309 {
310 let oldValue = this._hitCount;
311 this._hitCount = value;
312 FilterNotifier.triggerListeners("filter.hitCount", this, value, oldValue);
313 }
314 return this._hitCount;
315 },
316
317 /**
318 * Last time the filter had a hit (in milliseconds since the beginning of the epoch)
319 * @type Number
320 */
321 get lastHit()
322 {
323 return this._lastHit;
324 },
325 set lastHit(value)
326 {
327 if (value != this._lastHit)
328 {
329 let oldValue = this._lastHit;
330 this._lastHit = value;
331 FilterNotifier.triggerListeners("filter.lastHit", this, value, oldValue);
332 }
333 return this._lastHit;
334 },
335
336 /**
337 * String that the domains property should be generated from
338 * @type String
339 */
340 domainSource: null,
341
342 /**
343 * Separator character used in domainSource property, must be overridden by su bclasses
344 * @type String
345 */
346 domainSeparator: null,
347
348 /**
349 * Determines whether the trailing dot in domain names isn't important and
350 * should be ignored, must be overridden by subclasses.
351 * @type Boolean
352 */
353 ignoreTrailingDot: true,
354
355 /**
356 * Determines whether domainSource is already upper-case,
357 * can be overridden by subclasses.
358 * @type Boolean
359 */
360 domainSourceIsUpperCase: false,
361
362 /**
363 * Map containing domains that this filter should match on/not match on or nul l if the filter should match on all domains
364 * @type Object
365 */
366 get domains()
367 {
368 // Despite this property being cached, the getter is called
369 // several times on Safari, due to WebKit bug 132872
370 let prop = Object.getOwnPropertyDescriptor(this, "domains");
371 if (prop)
372 return prop.value;
373
374 let domains = null;
375
376 if (this.domainSource)
377 {
378 let source = this.domainSource;
379 if (!this.domainSourceIsUpperCase) {
380 // RegExpFilter already have uppercase domains
381 source = source.toUpperCase();
382 }
383 let list = source.split(this.domainSeparator);
384 if (list.length == 1 && list[0][0] != "~")
385 {
386 // Fast track for the common one-domain scenario
387 domains = {__proto__: null, "": false};
388 if (this.ignoreTrailingDot)
389 list[0] = list[0].replace(/\.+$/, "");
390 domains[list[0]] = true;
391 }
392 else
393 {
394 let hasIncludes = false;
395 for (let i = 0; i < list.length; i++)
396 {
397 let domain = list[i];
398 if (this.ignoreTrailingDot)
399 domain = domain.replace(/\.+$/, "");
400 if (domain == "")
401 continue;
402
403 let include;
404 if (domain[0] == "~")
405 {
406 include = false;
407 domain = domain.substr(1);
408 }
409 else
410 {
411 include = true;
412 hasIncludes = true;
413 }
414
415 if (!domains)
416 domains = Object.create(null);
417
418 domains[domain] = include;
419 }
420 domains[""] = !hasIncludes;
421 }
422
423 this.domainSource = null;
424 }
425
426 Object.defineProperty(this, "domains", {value: domains, enumerable: true});
427 return this.domains;
428 },
429
430 /**
431 * Array containing public keys of websites that this filter should apply to
432 * @type string[]
433 */
434 sitekeys: null,
435
436 /**
437 * Checks whether this filter is active on a domain.
438 * @param {String} docDomain domain name of the document that loads the URL
439 * @param {String} [sitekey] public key provided by the document
440 * @return {Boolean} true in case of the filter being active
441 */
442 isActiveOnDomain: function(docDomain, sitekey)
443 {
444 // Sitekeys are case-sensitive so we shouldn't convert them to upper-case to avoid false
445 // positives here. Instead we need to change the way filter options are pars ed.
446 if (this.sitekeys && (!sitekey || this.sitekeys.indexOf(sitekey.toUpperCase( )) < 0))
447 return false;
448
449 // If no domains are set the rule matches everywhere
450 if (!this.domains)
451 return true;
452
453 // If the document has no host name, match only if the filter isn't restrict ed to specific domains
454 if (!docDomain)
455 return this.domains[""];
456
457 if (this.ignoreTrailingDot)
458 docDomain = docDomain.replace(/\.+$/, "");
459 docDomain = docDomain.toUpperCase();
460
461 while (true)
462 {
463 if (docDomain in this.domains)
464 return this.domains[docDomain];
465
466 let nextDot = docDomain.indexOf(".");
467 if (nextDot < 0)
468 break;
469 docDomain = docDomain.substr(nextDot + 1);
470 }
471 return this.domains[""];
472 },
473
474 /**
475 * Checks whether this filter is active only on a domain and its subdomains.
476 */
477 isActiveOnlyOnDomain: function(/**String*/ docDomain) /**Boolean*/
478 {
479 if (!docDomain || !this.domains || this.domains[""])
480 return false;
481
482 if (this.ignoreTrailingDot)
483 docDomain = docDomain.replace(/\.+$/, "");
484 docDomain = docDomain.toUpperCase();
485
486 for (let domain in this.domains)
487 if (this.domains[domain] && domain != docDomain && (domain.length <= docDo main.length || domain.indexOf("." + docDomain) != domain.length - docDomain.leng th - 1))
488 return false;
489
490 return true;
491 },
492
493 /**
494 * Checks whether this filter is generic or specific
495 */
496 isGeneric: function() /**Boolean*/
497 {
498 return !(this.sitekeys && this.sitekeys.length) &&
499 (!this.domains || this.domains[""]);
500 },
501
502 /**
503 * See Filter.serialize()
504 */
505 serialize: function(buffer)
506 {
507 if (this._disabled || this._hitCount || this._lastHit)
508 {
509 Filter.prototype.serialize.call(this, buffer);
510 if (this._disabled)
511 buffer.push("disabled=true");
512 if (this._hitCount)
513 buffer.push("hitCount=" + this._hitCount);
514 if (this._lastHit)
515 buffer.push("lastHit=" + this._lastHit);
516 }
517 }
518 };
519
520 /**
521 * Abstract base class for RegExp-based filters
522 * @param {String} text see Filter()
523 * @param {String} regexpSource filter part that the regular expression should b e build from
524 * @param {Number} [contentType] Content types the filter applies to, combinatio n of values from RegExpFilter.typeMap
525 * @param {Boolean} [matchCase] Defines whether the filter should distinguish be tween lower and upper case letters
526 * @param {String} [domains] Domains that the filter is restricted to, e.g. "foo .com|bar.com|~baz.com"
527 * @param {Boolean} [thirdParty] Defines whether the filter should apply to thir d-party or first-party content only
528 * @param {String} [sitekeys] Public keys of websites that this filter should ap ply to
529 * @constructor
530 * @augments ActiveFilter
531 */
532 function RegExpFilter(text, regexpSource, contentType, matchCase, domains, third Party, sitekeys)
533 {
534 ActiveFilter.call(this, text, domains, sitekeys);
535
536 if (contentType != null)
537 this.contentType = contentType;
538 if (matchCase)
539 this.matchCase = matchCase;
540 if (thirdParty != null)
541 this.thirdParty = thirdParty;
542 if (sitekeys != null)
543 this.sitekeySource = sitekeys;
544
545 if (regexpSource.length >= 2 && regexpSource[0] == "/" && regexpSource[regexpS ource.length - 1] == "/")
546 {
547 // The filter is a regular expression - convert it immediately to catch synt ax errors
548 let regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), thi s.matchCase ? "" : "i");
549 Object.defineProperty(this, "regexp", {value: regexp});
550 }
551 else
552 {
553 // No need to convert this filter to regular expression yet, do it on demand
554 this.regexpSource = regexpSource;
555 }
556 }
557 exports.RegExpFilter = RegExpFilter;
558
559 RegExpFilter.prototype =
560 {
561 __proto__: ActiveFilter.prototype,
562
563 /**
564 * @see ActiveFilter.domainSourceIsUpperCase
565 */
566 domainSourceIsUpperCase: true,
567
568 /**
569 * Number of filters contained, will always be 1 (required to optimize Matcher ).
570 * @type Integer
571 */
572 length: 1,
573
574 /**
575 * @see ActiveFilter.domainSeparator
576 */
577 domainSeparator: "|",
578
579 /**
580 * Expression from which a regular expression should be generated - for delaye d creation of the regexp property
581 * @type String
582 */
583 regexpSource: null,
584 /**
585 * Regular expression to be used when testing against this filter
586 * @type RegExp
587 */
588 get regexp()
589 {
590 // Despite this property being cached, the getter is called
591 // several times on Safari, due to WebKit bug 132872
592 let prop = Object.getOwnPropertyDescriptor(this, "regexp");
593 if (prop)
594 return prop.value;
595
596 let source = Filter.toRegExp(this.regexpSource);
597 let regexp = new RegExp(source, this.matchCase ? "" : "i");
598 Object.defineProperty(this, "regexp", {value: regexp});
599 return regexp;
600 },
601 /**
602 * Content types the filter applies to, combination of values from RegExpFilte r.typeMap
603 * @type Number
604 */
605 contentType: 0x7FFFFFFF,
606 /**
607 * Defines whether the filter should distinguish between lower and upper case letters
608 * @type Boolean
609 */
610 matchCase: false,
611 /**
612 * Defines whether the filter should apply to third-party or first-party conte nt only. Can be null (apply to all content).
613 * @type Boolean
614 */
615 thirdParty: null,
616
617 /**
618 * String that the sitekey property should be generated from
619 * @type String
620 */
621 sitekeySource: null,
622
623 /**
624 * Array containing public keys of websites that this filter should apply to
625 * @type string[]
626 */
627 get sitekeys()
628 {
629 // Despite this property being cached, the getter is called
630 // several times on Safari, due to WebKit bug 132872
631 let prop = Object.getOwnPropertyDescriptor(this, "sitekeys");
632 if (prop)
633 return prop.value;
634
635 let sitekeys = null;
636
637 if (this.sitekeySource)
638 {
639 sitekeys = this.sitekeySource.split("|");
640 this.sitekeySource = null;
641 }
642
643 Object.defineProperty(this, "sitekeys", {value: sitekeys, enumerable: true}) ;
644 return this.sitekeys;
645 },
646
647 /**
648 * Tests whether the URL matches this filter
649 * @param {String} location URL to be tested
650 * @param {String} typeMask bitmask of content / request types to match
651 * @param {String} docDomain domain name of the document that loads the URL
652 * @param {Boolean} thirdParty should be true if the URL is a third-party requ est
653 * @param {String} sitekey public key provided by the document
654 * @return {Boolean} true in case of a match
655 */
656 matches: function(location, typeMask, docDomain, thirdParty, sitekey)
657 {
658 if (this.contentType & typeMask &&
659 (this.thirdParty == null || this.thirdParty == thirdParty) &&
660 this.isActiveOnDomain(docDomain, sitekey) && this.regexp.test(location))
661 {
662 return true;
663 }
664
665 return false;
666 }
667 };
668
669 // Required to optimize Matcher, see also RegExpFilter.prototype.length
670 Object.defineProperty(RegExpFilter.prototype, "0",
671 {
672 get: function() { return this; }
673 });
674
675 /**
676 * Creates a RegExp filter from its text representation
677 * @param {String} text same as in Filter()
678 */
679 RegExpFilter.fromText = function(text)
680 {
681 let blocking = true;
682 let origText = text;
683 if (text.indexOf("@@") == 0)
684 {
685 blocking = false;
686 text = text.substr(2);
687 }
688
689 let contentType = null;
690 let matchCase = null;
691 let domains = null;
692 let sitekeys = null;
693 let thirdParty = null;
694 let collapse = null;
695 let options;
696 let match = (text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null);
697 if (match)
698 {
699 options = match[1].toUpperCase().split(",");
700 text = match.input.substr(0, match.index);
701 for (let option of options)
702 {
703 let value = null;
704 let separatorIndex = option.indexOf("=");
705 if (separatorIndex >= 0)
706 {
707 value = option.substr(separatorIndex + 1);
708 option = option.substr(0, separatorIndex);
709 }
710 option = option.replace(/-/, "_");
711 if (option in RegExpFilter.typeMap)
712 {
713 if (contentType == null)
714 contentType = 0;
715 contentType |= RegExpFilter.typeMap[option];
716 }
717 else if (option[0] == "~" && option.substr(1) in RegExpFilter.typeMap)
718 {
719 if (contentType == null)
720 contentType = RegExpFilter.prototype.contentType;
721 contentType &= ~RegExpFilter.typeMap[option.substr(1)];
722 }
723 else if (option == "MATCH_CASE")
724 matchCase = true;
725 else if (option == "~MATCH_CASE")
726 matchCase = false;
727 else if (option == "DOMAIN" && typeof value != "undefined")
728 domains = value;
729 else if (option == "THIRD_PARTY")
730 thirdParty = true;
731 else if (option == "~THIRD_PARTY")
732 thirdParty = false;
733 else if (option == "COLLAPSE")
734 collapse = true;
735 else if (option == "~COLLAPSE")
736 collapse = false;
737 else if (option == "SITEKEY" && typeof value != "undefined")
738 sitekeys = value;
739 else
740 return new InvalidFilter(origText, "Unknown option " + option.toLowerCas e());
741 }
742 }
743
744 try
745 {
746 if (blocking)
747 return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys, collapse);
748 else
749 return new WhitelistFilter(origText, text, contentType, matchCase, domains , thirdParty, sitekeys);
750 }
751 catch (e)
752 {
753 return new InvalidFilter(origText, e);
754 }
755 };
756
757 /**
758 * Maps type strings like "SCRIPT" or "OBJECT" to bit masks
759 */
760 RegExpFilter.typeMap = {
761 OTHER: 1,
762 SCRIPT: 2,
763 IMAGE: 4,
764 STYLESHEET: 8,
765 OBJECT: 16,
766 SUBDOCUMENT: 32,
767 DOCUMENT: 64,
768 XBL: 1,
769 PING: 1024,
770 XMLHTTPREQUEST: 2048,
771 OBJECT_SUBREQUEST: 4096,
772 DTD: 1,
773 MEDIA: 16384,
774 FONT: 32768,
775
776 BACKGROUND: 4, // Backwards compat, same as IMAGE
777
778 POPUP: 0x10000000,
779 GENERICBLOCK: 0x20000000,
780 ELEMHIDE: 0x40000000,
781 GENERICHIDE: 0x80000000
782 };
783
784 // DOCUMENT, ELEMHIDE, POPUP, GENERICHIDE and GENERICBLOCK options shouldn't
785 // be there by default
786 RegExpFilter.prototype.contentType &= ~(RegExpFilter.typeMap.DOCUMENT |
787 RegExpFilter.typeMap.ELEMHIDE |
788 RegExpFilter.typeMap.POPUP |
789 RegExpFilter.typeMap.GENERICHIDE |
790 RegExpFilter.typeMap.GENERICBLOCK);
791
792 /**
793 * Class for blocking filters
794 * @param {String} text see Filter()
795 * @param {String} regexpSource see RegExpFilter()
796 * @param {Number} contentType see RegExpFilter()
797 * @param {Boolean} matchCase see RegExpFilter()
798 * @param {String} domains see RegExpFilter()
799 * @param {Boolean} thirdParty see RegExpFilter()
800 * @param {String} sitekeys see RegExpFilter()
801 * @param {Boolean} collapse defines whether the filter should collapse blocked content, can be null
802 * @constructor
803 * @augments RegExpFilter
804 */
805 function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thi rdParty, sitekeys, collapse)
806 {
807 RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, t hirdParty, sitekeys);
808
809 this.collapse = collapse;
810 }
811 exports.BlockingFilter = BlockingFilter;
812
813 BlockingFilter.prototype =
814 {
815 __proto__: RegExpFilter.prototype,
816
817 type: "blocking",
818
819 /**
820 * Defines whether the filter should collapse blocked content. Can be null (us e the global preference).
821 * @type Boolean
822 */
823 collapse: null
824 };
825
826 /**
827 * Class for whitelist filters
828 * @param {String} text see Filter()
829 * @param {String} regexpSource see RegExpFilter()
830 * @param {Number} contentType see RegExpFilter()
831 * @param {Boolean} matchCase see RegExpFilter()
832 * @param {String} domains see RegExpFilter()
833 * @param {Boolean} thirdParty see RegExpFilter()
834 * @param {String} sitekeys see RegExpFilter()
835 * @constructor
836 * @augments RegExpFilter
837 */
838 function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, th irdParty, sitekeys)
839 {
840 RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, t hirdParty, sitekeys);
841 }
842 exports.WhitelistFilter = WhitelistFilter;
843
844 WhitelistFilter.prototype =
845 {
846 __proto__: RegExpFilter.prototype,
847
848 type: "whitelist"
849 };
850
851 /**
852 * Base class for element hiding filters
853 * @param {String} text see Filter()
854 * @param {String} [domains] Host names or domains the filter should be restrict ed to
855 * @param {String} selector CSS selector for the HTML elements that should be hidden
856 * @constructor
857 * @augments ActiveFilter
858 */
859 function ElemHideBase(text, domains, selector)
860 {
861 ActiveFilter.call(this, text, domains || null);
862
863 if (domains)
864 this.selectorDomain = domains.replace(/,~[^,]+/g, "").replace(/^~[^,]+,?/, " ").toLowerCase();
865 this.selector = selector;
866 }
867 exports.ElemHideBase = ElemHideBase;
868
869 ElemHideBase.prototype =
870 {
871 __proto__: ActiveFilter.prototype,
872
873 /**
874 * @see ActiveFilter.domainSeparator
875 */
876 domainSeparator: ",",
877
878 /**
879 * @see ActiveFilter.ignoreTrailingDot
880 */
881 ignoreTrailingDot: false,
882
883 /**
884 * Host name or domain the filter should be restricted to (can be null for no restriction)
885 * @type String
886 */
887 selectorDomain: null,
888 /**
889 * CSS selector for the HTML elements that should be hidden
890 * @type String
891 */
892 selector: null
893 };
894
895 /**
896 * Creates an element hiding filter from a pre-parsed text representation
897 *
898 * @param {String} text same as in Filter()
899 * @param {String} domain domain part of the text representation (can be e mpty)
900 * @param {Boolean} isException exception rule indicator
901 * @param {String} tagName tag name part (can be empty)
902 * @param {String} attrRules attribute matching rules (can be empty)
903 * @param {String} selector raw CSS selector (can be empty)
904 * @return {ElemHideFilter|ElemHideException|CSSPropertyFilter|InvalidFilter}
905 */
906 ElemHideBase.fromText = function(text, domain, isException, tagName, attrRules, selector)
907 {
908 if (!selector)
909 {
910 if (tagName == "*")
911 tagName = "";
912
913 let id = null;
914 let additional = "";
915 if (attrRules)
916 {
917 attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g);
918 for (let rule of attrRules)
919 {
920 rule = rule.substr(1, rule.length - 2);
921 let separatorPos = rule.indexOf("=");
922 if (separatorPos > 0)
923 {
924 rule = rule.replace(/=/, '="') + '"';
925 additional += "[" + rule + "]";
926 }
927 else
928 {
929 if (id)
930 return new InvalidFilter(text, Utils.getString("filter_elemhide_dupl icate_id"));
931
932 id = rule;
933 }
934 }
935 }
936
937 if (id)
938 selector = tagName + "." + id + additional + "," + tagName + "#" + id + ad ditional;
939 else if (tagName || additional)
940 selector = tagName + additional;
941 else
942 return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria "));
943 }
944
945 if (isException)
946 return new ElemHideException(text, domain, selector);
947
948 let match = Filter.csspropertyRegExp.exec(selector);
949 if (match)
950 {
951 // CSS property filters are inefficient so we need to make sure that
952 // they're only applied if they specify active domains
953 if (!/,[^~][^,.]*\.[^,]/.test("," + domain))
954 return new InvalidFilter(text, Utils.getString("filter_cssproperty_nodomai n"));
955
956 return new CSSPropertyFilter(text, domain, selector, match[2],
957 selector.substr(0, match.index),
958 selector.substr(match.index + match[0].length));
959 }
960
961 return new ElemHideFilter(text, domain, selector);
962 };
963
964 /**
965 * Class for element hiding filters
966 * @param {String} text see Filter()
967 * @param {String} domains see ElemHideBase()
968 * @param {String} selector see ElemHideBase()
969 * @constructor
970 * @augments ElemHideBase
971 */
972 function ElemHideFilter(text, domains, selector)
973 {
974 ElemHideBase.call(this, text, domains, selector);
975 }
976 exports.ElemHideFilter = ElemHideFilter;
977
978 ElemHideFilter.prototype =
979 {
980 __proto__: ElemHideBase.prototype,
981
982 type: "elemhide"
983 };
984
985 /**
986 * Class for element hiding exceptions
987 * @param {String} text see Filter()
988 * @param {String} domains see ElemHideBase()
989 * @param {String} selector see ElemHideBase()
990 * @constructor
991 * @augments ElemHideBase
992 */
993 function ElemHideException(text, domains, selector)
994 {
995 ElemHideBase.call(this, text, domains, selector);
996 }
997 exports.ElemHideException = ElemHideException;
998
999 ElemHideException.prototype =
1000 {
1001 __proto__: ElemHideBase.prototype,
1002
1003 type: "elemhideexception"
1004 };
1005
1006 /**
1007 * Class for CSS property filters
1008 * @param {String} text see Filter()
1009 * @param {String} domains see ElemHideBase()
1010 * @param {String} selector see ElemHideBase()
1011 * @param {String} regexpSource see CSSPropertyFilter.regexpSource
1012 * @param {String} selectorPrefix see CSSPropertyFilter.selectorPrefix
1013 * @param {String} selectorSuffix see CSSPropertyFilter.selectorSuffix
1014 * @constructor
1015 * @augments ElemHideBase
1016 */
1017 function CSSPropertyFilter(text, domains, selector, regexpSource,
1018 selectorPrefix, selectorSuffix)
1019 {
1020 ElemHideBase.call(this, text, domains, selector);
1021
1022 this.regexpSource = regexpSource;
1023 this.selectorPrefix = selectorPrefix;
1024 this.selectorSuffix = selectorSuffix;
1025 }
1026 exports.CSSPropertyFilter = CSSPropertyFilter;
1027
1028 CSSPropertyFilter.prototype =
1029 {
1030 __proto__: ElemHideBase.prototype,
1031
1032 type: "cssproperty",
1033
1034 /**
1035 * Expression from which a regular expression should be generated for matching
1036 * CSS properties - for delayed creation of the regexpString property
1037 * @type String
1038 */
1039 regexpSource: null,
1040 /**
1041 * Substring of CSS selector before properties for the HTML elements that
1042 * should be hidden
1043 * @type String
1044 */
1045 selectorPrefix: null,
1046 /**
1047 * Substring of CSS selector after properties for the HTML elements that
1048 * should be hidden
1049 * @type String
1050 */
1051 selectorSuffix: null,
1052
1053 /**
1054 * Raw regular expression string to be used when testing CSS properties
1055 * against this filter
1056 * @type String
1057 */
1058 get regexpString()
1059 {
1060 // Despite this property being cached, the getter is called
1061 // several times on Safari, due to WebKit bug 132872
1062 let prop = Object.getOwnPropertyDescriptor(this, "regexpString");
1063 if (prop)
1064 return prop.value;
1065
1066 let regexp = Filter.toRegExp(this.regexpSource);
1067 Object.defineProperty(this, "regexpString", {value: regexp});
1068 return regexp;
1069 }
1070 };
OLDNEW
« no previous file with comments | « lib/elemHide.js ('k') | lib/filterListener.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld