| OLD | NEW | 
|---|
|  | (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 }; |  | 
| OLD | NEW | 
|---|