| Index: lib/subscriptionClasses.js | 
| =================================================================== | 
| --- a/lib/subscriptionClasses.js | 
| +++ b/lib/subscriptionClasses.js | 
| @@ -16,32 +16,33 @@ | 
| */ | 
|  | 
| "use strict"; | 
|  | 
| /** | 
| * @fileOverview Definition of Subscription class and its subclasses. | 
| */ | 
|  | 
| -const {ActiveFilter, BlockingFilter, | 
| +const {Filter, ActiveFilter, BlockingFilter, | 
| WhitelistFilter, ElemHideBase} = require("./filterClasses"); | 
| const {filterNotifier} = require("./filterNotifier"); | 
| const {extend} = require("./coreUtils"); | 
|  | 
| /** | 
| * Abstract base class for filter subscriptions | 
| * | 
| * @param {string} url    download location of the subscription | 
| * @param {string} [title]  title of the filter subscription | 
| * @constructor | 
| */ | 
| function Subscription(url, title) | 
| { | 
| this.url = url; | 
| -  this.filters = []; | 
| +  this._filterText = []; | 
| +  this._filters = []; | 
| if (title) | 
| this._title = title; | 
| Subscription.knownSubscriptions.set(url, this); | 
| } | 
| exports.Subscription = Subscription; | 
|  | 
| Subscription.prototype = | 
| { | 
| @@ -53,20 +54,29 @@ | 
|  | 
| /** | 
| * Type of the subscription | 
| * @type {?string} | 
| */ | 
| type: null, | 
|  | 
| /** | 
| -   * Filters contained in the filter subscription | 
| -   * @type {Filter[]} | 
| +   * Filter text contained in the filter subscription. | 
| +   * @type {Array.<string>} | 
| +   * @private | 
| */ | 
| -  filters: null, | 
| +  _filterText: null, | 
| + | 
| +  /** | 
| +   * Optional {@link Filter} objects corresponding to the subscription's filter | 
| +   * text. | 
| +   * @type {Array.<?Filter>} | 
| +   * @private | 
| +   */ | 
| +  _filters: null, | 
|  | 
| _title: null, | 
| _fixedTitle: false, | 
| _disabled: false, | 
|  | 
| /** | 
| * Title of the filter subscription | 
| * @type {string} | 
| @@ -120,16 +130,125 @@ | 
| let oldValue = this._disabled; | 
| this._disabled = value; | 
| filterNotifier.emit("subscription.disabled", this, value, oldValue); | 
| } | 
| return this._disabled; | 
| }, | 
|  | 
| /** | 
| +   * The number of filters in the subscription. | 
| +   * @type {number} | 
| +   */ | 
| +  get filterCount() | 
| +  { | 
| +    return this._filters.length; | 
| +  }, | 
| + | 
| +  /** | 
| +   * Yields the text for each filter in the subscription. | 
| +   * @yields {string} | 
| +   */ | 
| +  *filterText() | 
| +  { | 
| +    yield* this._filterText; | 
| +  }, | 
| + | 
| +  /** | 
| +   * Yields the {@link Filter} object for each filter in the subscription. | 
| +   * @yields {Filter} | 
| +   */ | 
| +  *filters() | 
| +  { | 
| +    for (let i = 0; i < this._filters.length; i++) | 
| +      yield this._filters[i] || Filter.fromText(this._filterText[i]); | 
| +  }, | 
| + | 
| +  /** | 
| +   * Returns the {@link Filter} object at the given 0-based index. | 
| +   * @param {number} index | 
| +   * @returns {?Filter} | 
| +   */ | 
| +  filterAt(index) | 
| +  { | 
| +    return this._filters[index] || | 
| +           (index < this._filters.length ? | 
| +              Filter.fromText(this._filterText[index]) : null); | 
| +  }, | 
| + | 
| +  /** | 
| +   * Returns the 0-based index of the given filter. | 
| +   * @param {Filter} filter | 
| +   * @param {number} [fromIndex] The index from which to start the search. | 
| +   * @return {number} | 
| +   */ | 
| +  searchFilter(filter, fromIndex = 0) | 
| +  { | 
| +    return this._filterText.indexOf(filter.text, fromIndex); | 
| +  }, | 
| + | 
| +  /** | 
| +   * Removes all filters from the subscription. | 
| +   */ | 
| +  clearFilters() | 
| +  { | 
| +    this._filterText = []; | 
| +    this._filters = []; | 
| +  }, | 
| + | 
| +  /** | 
| +   * Adds a filter to the subscription. | 
| +   * @param {Filter} filter | 
| +   * @param {boolean} [forceCache] If set to <code>true</code>, the | 
| +   *   {@link Filter} object is cached regardless of whether it's lightweight. | 
| +   *   By default lightweight objects are not cached but are recreated on | 
| +   *   demand. | 
| +   */ | 
| +  addFilter(filter, forceCache = false) | 
| +  { | 
| +    this._filterText.push(filter.text); | 
| +    this._filters.push(!forceCache && filter.lightweight ? null : filter); | 
| +  }, | 
| + | 
| +  /** | 
| +   * Inserts a filter into the subscription. | 
| +   * @param {Filter} filter | 
| +   * @param {number} [index] The index at which to insert the filter. | 
| +   */ | 
| +  insertFilterAt(filter, index) | 
| +  { | 
| +    this._filterText.splice(index, 0, filter.text); | 
| +    this._filters.splice(index, 0, filter.lightweight ? null : filter); | 
| +  }, | 
| + | 
| +  /** | 
| +   * Deletes a filter from the subscription. | 
| +   * @param {number} [index] The index at which to delete the filter. | 
| +   */ | 
| +  deleteFilterAt(index) | 
| +  { | 
| +    this._filterText.splice(index, 1); | 
| +    this._filters.splice(index, 1); | 
| +  }, | 
| + | 
| +  /** | 
| +   * Frees up any force-cached {@link Filter} objects. | 
| +   * @see #addFilter | 
| +   */ | 
| +  freeFilters() | 
| +  { | 
| +    for (let i = 0; i < this._filters.length; i++) | 
| +    { | 
| +      let filter = this._filters[i]; | 
| +      if (filter && filter.lightweight) | 
| +        this._filters[i] = null; | 
| +    } | 
| +  }, | 
| + | 
| +  /** | 
| * Serializes the subscription for writing out on disk. | 
| * @yields {string} | 
| */ | 
| *serialize() | 
| { | 
| let {url, type, _title, _fixedTitle, _disabled} = this; | 
|  | 
| yield "[Subscription]"; | 
| @@ -142,22 +261,22 @@ | 
| if (_fixedTitle) | 
| yield "fixedTitle=true"; | 
| if (_disabled) | 
| yield "disabled=true"; | 
| }, | 
|  | 
| *serializeFilters() | 
| { | 
| -    let {filters} = this; | 
| +    let {_filterText} = this; | 
|  | 
| yield "[Subscription filters]"; | 
|  | 
| -    for (let filter of filters) | 
| -      yield filter.text.replace(/\[/g, "\\["); | 
| +    for (let text of _filterText) | 
| +      yield text.replace(/\[/g, "\\["); | 
| }, | 
|  | 
| toString() | 
| { | 
| return [...this.serialize()].join("\n"); | 
| } | 
| }; | 
|  | 
| @@ -328,17 +447,17 @@ | 
| * Creates a new user-defined filter group and adds the given filter to it. | 
| * This group will act as the default group for this filter type. | 
| * @param {Filter} filter | 
| * @return {SpecialSubscription} | 
| */ | 
| SpecialSubscription.createForFilter = function(filter) | 
| { | 
| let subscription = SpecialSubscription.create(); | 
| -  subscription.filters.push(filter); | 
| +  subscription.addFilter(filter); | 
| for (let [type, class_] of SpecialSubscription.defaultsMap) | 
| { | 
| if (filter instanceof class_) | 
| subscription.defaults = [type]; | 
| } | 
| if (!subscription.defaults) | 
| subscription.defaults = ["blocking"]; | 
| return subscription; | 
|  |