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

Side by Side Diff: lib/filterStorage.js

Issue 29426559: Issue 5137 - [emscripten] Added basic filter storage implementation (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore
Patch Set: Improved type names and added finally block Created May 8, 2017, 12:53 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 | « compiled/subscription/UserDefinedSubscription.cpp ('k') | lib/subscriptionClasses.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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-2017 eyeo GmbH 3 * Copyright (C) 2006-2017 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 "use strict"; 18 "use strict";
19 19
20 /** 20 const {FilterStorage} = require("compiled");
21 * @fileOverview FilterStorage class responsible for managing user's 21 const {Subscription, SpecialSubscription} = require("subscriptionClasses");
22 * subscriptions and filters. 22 const {ActiveFilter} = require("filterClasses");
23 */
24 23
25 const {IO} = require("io"); 24 // Backwards compatibility
26 const {Prefs} = require("prefs"); 25 FilterStorage.getGroupForFilter = FilterStorage.getSubscriptionForFilter;
27 const {Filter, ActiveFilter} = require("filterClasses");
28 const {Subscription, SpecialSubscription,
29 ExternalSubscription} = require("subscriptionClasses");
30 const {FilterNotifier} = require("filterNotifier");
31 26
32 /** 27 /**
33 * Version number of the filter storage file format. 28 * This property allows iterating over the list of subscriptions. It will delete
34 * @type {number} 29 * references automatically at the end of the current loop iteration. If you
30 * need persistent references or element access by position you should use
31 * FilterStorage.subscriptionAt() instead.
32 * @type {Iterable}
35 */ 33 */
36 let formatVersion = 5; 34 FilterStorage.subscriptions = {
37 35 [Symbol.iterator]: function*()
38 /**
39 * This class reads user's filters from disk, manages them in memory
40 * and writes them back.
41 * @class
42 */
43 let FilterStorage = exports.FilterStorage =
44 {
45 /**
46 * Will be set to true after the initial loadFromDisk() call completes.
47 * @type {boolean}
48 */
49 initialized: false,
50
51 /**
52 * Version number of the patterns.ini format used.
53 * @type {number}
54 */
55 get formatVersion()
56 { 36 {
57 return formatVersion; 37 for (let i = 0, l = FilterStorage.subscriptionCount; i < l; i++)
58 },
59
60 /**
61 * File containing the filter list
62 * @type {string}
63 */
64 get sourceFile()
65 {
66 return "patterns.ini";
67 },
68
69 /**
70 * Will be set to true if no patterns.ini file exists.
71 * @type {boolean}
72 */
73 firstRun: false,
74
75 /**
76 * Map of properties listed in the filter storage file before the sections
77 * start. Right now this should be only the format version.
78 */
79 fileProperties: Object.create(null),
80
81 /**
82 * List of filter subscriptions containing all filters
83 * @type {Subscription[]}
84 */
85 subscriptions: [],
86
87 /**
88 * Map of subscriptions already on the list, by their URL/identifier
89 * @type {Object}
90 */
91 knownSubscriptions: Object.create(null),
92
93 /**
94 * Finds the filter group that a filter should be added to by default. Will
95 * return null if this group doesn't exist yet.
96 * @param {Filter} filter
97 * @return {?SpecialSubscription}
98 */
99 getGroupForFilter(filter)
100 {
101 let generalSubscription = null;
102 for (let subscription of FilterStorage.subscriptions)
103 { 38 {
104 if (subscription instanceof SpecialSubscription && !subscription.disabled) 39 let subscription = FilterStorage.subscriptionAt(i);
40 try
105 { 41 {
106 // Always prefer specialized subscriptions 42 yield subscription;
107 if (subscription.isDefaultFor(filter)) 43 }
108 return subscription; 44 finally
109 45 {
110 // If this is a general subscription - store it as fallback 46 subscription.delete();
111 if (!generalSubscription &&
112 (!subscription.defaults || !subscription.defaults.length))
113 {
114 generalSubscription = subscription;
115 }
116 } 47 }
117 } 48 }
118 return generalSubscription;
119 },
120
121 /**
122 * Adds a filter subscription to the list
123 * @param {Subscription} subscription filter subscription to be added
124 */
125 addSubscription(subscription)
126 {
127 if (subscription.url in FilterStorage.knownSubscriptions)
128 return;
129
130 FilterStorage.subscriptions.push(subscription);
131 FilterStorage.knownSubscriptions[subscription.url] = subscription;
132 addSubscriptionFilters(subscription);
133
134 FilterNotifier.triggerListeners("subscription.added", subscription);
135 },
136
137 /**
138 * Removes a filter subscription from the list
139 * @param {Subscription} subscription filter subscription to be removed
140 */
141 removeSubscription(subscription)
142 {
143 for (let i = 0; i < FilterStorage.subscriptions.length; i++)
144 {
145 if (FilterStorage.subscriptions[i].url == subscription.url)
146 {
147 removeSubscriptionFilters(subscription);
148
149 FilterStorage.subscriptions.splice(i--, 1);
150 delete FilterStorage.knownSubscriptions[subscription.url];
151 FilterNotifier.triggerListeners("subscription.removed", subscription);
152 return;
153 }
154 }
155 },
156
157 /**
158 * Moves a subscription in the list to a new position.
159 * @param {Subscription} subscription filter subscription to be moved
160 * @param {Subscription} [insertBefore] filter subscription to insert before
161 * (if omitted the subscription will be put at the end of the list)
162 */
163 moveSubscription(subscription, insertBefore)
164 {
165 let currentPos = FilterStorage.subscriptions.indexOf(subscription);
166 if (currentPos < 0)
167 return;
168
169 let newPos = -1;
170 if (insertBefore)
171 newPos = FilterStorage.subscriptions.indexOf(insertBefore);
172
173 if (newPos < 0)
174 newPos = FilterStorage.subscriptions.length;
175
176 if (currentPos < newPos)
177 newPos--;
178 if (currentPos == newPos)
179 return;
180
181 FilterStorage.subscriptions.splice(currentPos, 1);
182 FilterStorage.subscriptions.splice(newPos, 0, subscription);
183 FilterNotifier.triggerListeners("subscription.moved", subscription);
184 },
185
186 /**
187 * Replaces the list of filters in a subscription by a new list
188 * @param {Subscription} subscription filter subscription to be updated
189 * @param {Filter[]} filters new filter list
190 */
191 updateSubscriptionFilters(subscription, filters)
192 {
193 removeSubscriptionFilters(subscription);
194 subscription.oldFilters = subscription.filters;
195 subscription.filters = filters;
196 addSubscriptionFilters(subscription);
197 FilterNotifier.triggerListeners("subscription.updated", subscription);
198 delete subscription.oldFilters;
199 },
200
201 /**
202 * Adds a user-defined filter to the list
203 * @param {Filter} filter
204 * @param {SpecialSubscription} [subscription]
205 * particular group that the filter should be added to
206 * @param {number} [position]
207 * position within the subscription at which the filter should be added
208 */
209 addFilter(filter, subscription, position)
210 {
211 if (!subscription)
212 {
213 if (filter.subscriptions.some(s => s instanceof SpecialSubscription &&
214 !s.disabled))
215 {
216 return; // No need to add
217 }
218 subscription = FilterStorage.getGroupForFilter(filter);
219 }
220 if (!subscription)
221 {
222 // No group for this filter exists, create one
223 subscription = SpecialSubscription.createForFilter(filter);
224 this.addSubscription(subscription);
225 return;
226 }
227
228 if (typeof position == "undefined")
229 position = subscription.filters.length;
230
231 if (filter.subscriptions.indexOf(subscription) < 0)
232 filter.subscriptions.push(subscription);
233 subscription.filters.splice(position, 0, filter);
234 FilterNotifier.triggerListeners("filter.added", filter, subscription,
235 position);
236 },
237
238 /**
239 * Removes a user-defined filter from the list
240 * @param {Filter} filter
241 * @param {SpecialSubscription} [subscription] a particular filter group that
242 * the filter should be removed from (if ommited will be removed from all
243 * subscriptions)
244 * @param {number} [position] position inside the filter group at which the
245 * filter should be removed (if ommited all instances will be removed)
246 */
247 removeFilter(filter, subscription, position)
248 {
249 let subscriptions = (
250 subscription ? [subscription] : filter.subscriptions.slice()
251 );
252 for (let i = 0; i < subscriptions.length; i++)
253 {
254 let currentSubscription = subscriptions[i];
255 if (currentSubscription instanceof SpecialSubscription)
256 {
257 let positions = [];
258 if (typeof position == "undefined")
259 {
260 let index = -1;
261 do
262 {
263 index = currentSubscription.filters.indexOf(filter, index + 1);
264 if (index >= 0)
265 positions.push(index);
266 } while (index >= 0);
267 }
268 else
269 positions.push(position);
270
271 for (let j = positions.length - 1; j >= 0; j--)
272 {
273 let currentPosition = positions[j];
274 if (currentSubscription.filters[currentPosition] == filter)
275 {
276 currentSubscription.filters.splice(currentPosition, 1);
277 if (currentSubscription.filters.indexOf(filter) < 0)
278 {
279 let index = filter.subscriptions.indexOf(currentSubscription);
280 if (index >= 0)
281 filter.subscriptions.splice(index, 1);
282 }
283 FilterNotifier.triggerListeners(
284 "filter.removed", filter, currentSubscription, currentPosition
285 );
286 }
287 }
288 }
289 }
290 },
291
292 /**
293 * Moves a user-defined filter to a new position
294 * @param {Filter} filter
295 * @param {SpecialSubscription} subscription filter group where the filter is
296 * located
297 * @param {number} oldPosition current position of the filter
298 * @param {number} newPosition new position of the filter
299 */
300 moveFilter(filter, subscription, oldPosition, newPosition)
301 {
302 if (!(subscription instanceof SpecialSubscription) ||
303 subscription.filters[oldPosition] != filter)
304 {
305 return;
306 }
307
308 newPosition = Math.min(Math.max(newPosition, 0),
309 subscription.filters.length - 1);
310 if (oldPosition == newPosition)
311 return;
312
313 subscription.filters.splice(oldPosition, 1);
314 subscription.filters.splice(newPosition, 0, filter);
315 FilterNotifier.triggerListeners("filter.moved", filter, subscription,
316 oldPosition, newPosition);
317 },
318
319 /**
320 * Increases the hit count for a filter by one
321 * @param {Filter} filter
322 */
323 increaseHitCount(filter)
324 {
325 if (!Prefs.savestats || !(filter instanceof ActiveFilter))
326 return;
327
328 filter.hitCount++;
329 filter.lastHit = Date.now();
330 },
331
332 /**
333 * Resets hit count for some filters
334 * @param {Filter[]} filters filters to be reset, if null all filters will
335 * be reset
336 */
337 resetHitCounts(filters)
338 {
339 if (!filters)
340 {
341 filters = [];
342 for (let text in Filter.knownFilters)
343 filters.push(Filter.knownFilters[text]);
344 }
345 for (let filter of filters)
346 {
347 filter.hitCount = 0;
348 filter.lastHit = 0;
349 }
350 },
351
352 /**
353 * @callback TextSink
354 * @param {string?} line
355 */
356
357 /**
358 * Allows importing previously serialized filter data.
359 * @param {boolean} silent
360 * If true, no "load" notification will be sent out.
361 * @return {TextSink}
362 * Function to be called for each line of data. Calling it with null as
363 * parameter finalizes the import and replaces existing data. No changes
364 * will be applied before finalization, so import can be "aborted" by
365 * forgetting this callback.
366 */
367 importData(silent)
368 {
369 let parser = new INIParser();
370 return line =>
371 {
372 parser.process(line);
373 if (line === null)
374 {
375 let knownSubscriptions = Object.create(null);
376 for (let subscription of parser.subscriptions)
377 knownSubscriptions[subscription.url] = subscription;
378
379 this.fileProperties = parser.fileProperties;
380 this.subscriptions = parser.subscriptions;
381 this.knownSubscriptions = knownSubscriptions;
382 Filter.knownFilters = parser.knownFilters;
383 Subscription.knownSubscriptions = parser.knownSubscriptions;
384
385 if (!silent)
386 FilterNotifier.triggerListeners("load");
387 }
388 };
389 },
390
391 /**
392 * Loads all subscriptions from the disk.
393 * @return {Promise} promise resolved or rejected when loading is complete
394 */
395 loadFromDisk()
396 {
397 let tryBackup = backupIndex =>
398 {
399 return this.restoreBackup(backupIndex, true).then(() =>
400 {
401 if (this.subscriptions.length == 0)
402 return tryBackup(backupIndex + 1);
403 }).catch(error =>
404 {
405 // Give up
406 });
407 };
408
409 return IO.statFile(this.sourceFile).then(statData =>
410 {
411 if (!statData.exists)
412 {
413 this.firstRun = true;
414 return;
415 }
416
417 let parser = this.importData(true);
418 return IO.readFromFile(this.sourceFile, parser).then(() =>
419 {
420 parser(null);
421 if (this.subscriptions.length == 0)
422 {
423 // No filter subscriptions in the file, this isn't right.
424 throw new Error("No data in the file");
425 }
426 });
427 }).catch(error =>
428 {
429 Cu.reportError(error);
430 return tryBackup(1);
431 }).then(() =>
432 {
433 this.initialized = true;
434 FilterNotifier.triggerListeners("load");
435 });
436 },
437
438 /**
439 * Constructs the file name for a patterns.ini backup.
440 * @param {number} backupIndex
441 * number of the backup file (1 being the most recent)
442 * @return {string} backup file name
443 */
444 getBackupName(backupIndex)
445 {
446 let [name, extension] = this.sourceFile.split(".", 2);
447 return (name + "-backup" + backupIndex + "." + extension);
448 },
449
450 /**
451 * Restores an automatically created backup.
452 * @param {number} backupIndex
453 * number of the backup to restore (1 being the most recent)
454 * @param {boolean} silent
455 * If true, no "load" notification will be sent out.
456 * @return {Promise} promise resolved or rejected when restoring is complete
457 */
458 restoreBackup(backupIndex, silent)
459 {
460 let backupFile = this.getBackupName(backupIndex);
461 let parser = this.importData(silent);
462 return IO.readFromFile(backupFile, parser).then(() =>
463 {
464 parser(null);
465 return this.saveToDisk();
466 });
467 },
468
469 /**
470 * Generator serializing filter data and yielding it line by line.
471 */
472 *exportData()
473 {
474 // Do not persist external subscriptions
475 let subscriptions = this.subscriptions.filter(
476 s => !(s instanceof ExternalSubscription)
477 );
478
479 yield "# Adblock Plus preferences";
480 yield "version=" + formatVersion;
481
482 let saved = new Set();
483 let buf = [];
484
485 // Save subscriptions
486 for (let subscription of subscriptions)
487 {
488 yield "";
489
490 subscription.serialize(buf);
491 if (subscription.filters.length)
492 {
493 buf.push("", "[Subscription filters]");
494 subscription.serializeFilters(buf);
495 }
496 for (let line of buf)
497 yield line;
498 buf.splice(0);
499 }
500
501 // Save filter data
502 for (let subscription of subscriptions)
503 {
504 for (let filter of subscription.filters)
505 {
506 if (!saved.has(filter.text))
507 {
508 filter.serialize(buf);
509 saved.add(filter.text);
510 for (let line of buf)
511 yield line;
512 buf.splice(0);
513 }
514 }
515 }
516 },
517
518 /**
519 * Will be set to true if saveToDisk() is running (reentrance protection).
520 * @type {boolean}
521 */
522 _saving: false,
523
524 /**
525 * Will be set to true if a saveToDisk() call arrives while saveToDisk() is
526 * already running (delayed execution).
527 * @type {boolean}
528 */
529 _needsSave: false,
530
531 /**
532 * Saves all subscriptions back to disk
533 * @return {Promise} promise resolved or rejected when saving is complete
534 */
535 saveToDisk()
536 {
537 if (this._saving)
538 {
539 this._needsSave = true;
540 return;
541 }
542
543 this._saving = true;
544
545 return Promise.resolve().then(() =>
546 {
547 // First check whether we need to create a backup
548 if (Prefs.patternsbackups <= 0)
549 return false;
550
551 return IO.statFile(this.sourceFile).then(statData =>
552 {
553 if (!statData.exists)
554 return false;
555
556 return IO.statFile(this.getBackupName(1)).then(backupStatData =>
557 {
558 if (backupStatData.exists &&
559 (Date.now() - backupStatData.lastModified) / 3600000 <
560 Prefs.patternsbackupinterval)
561 {
562 return false;
563 }
564 return true;
565 });
566 });
567 }).then(backupRequired =>
568 {
569 if (!backupRequired)
570 return;
571
572 let ignoreErrors = error =>
573 {
574 // Expected error, backup file doesn't exist.
575 };
576
577 let renameBackup = index =>
578 {
579 if (index > 0)
580 {
581 return IO.renameFile(this.getBackupName(index),
582 this.getBackupName(index + 1))
583 .catch(ignoreErrors)
584 .then(() => renameBackup(index - 1));
585 }
586
587 return IO.renameFile(this.sourceFile, this.getBackupName(1))
588 .catch(ignoreErrors);
589 };
590
591 // Rename existing files
592 return renameBackup(Prefs.patternsbackups - 1);
593 }).catch(error =>
594 {
595 // Errors during backup creation shouldn't prevent writing filters.
596 Cu.reportError(error);
597 }).then(() =>
598 {
599 return IO.writeToFile(this.sourceFile, this.exportData());
600 }).then(() =>
601 {
602 FilterNotifier.triggerListeners("save");
603 }).catch(error =>
604 {
605 // If saving failed, report error but continue - we still have to process
606 // flags.
607 Cu.reportError(error);
608 }).then(() =>
609 {
610 this._saving = false;
611 if (this._needsSave)
612 {
613 this._needsSave = false;
614 this.saveToDisk();
615 }
616 });
617 },
618
619 /**
620 * @typedef FileInfo
621 * @type {object}
622 * @property {nsIFile} file
623 * @property {number} lastModified
624 */
625
626 /**
627 * Returns a promise resolving in a list of existing backup files.
628 * @return {Promise.<FileInfo[]>}
629 */
630 getBackupFiles()
631 {
632 let backups = [];
633
634 let checkBackupFile = index =>
635 {
636 return IO.statFile(this.getBackupName(index)).then(statData =>
637 {
638 if (!statData.exists)
639 return backups;
640
641 backups.push({
642 index,
643 lastModified: statData.lastModified
644 });
645 return checkBackupFile(index + 1);
646 }).catch(error =>
647 {
648 // Something went wrong, return whatever data we got so far.
649 Cu.reportError(error);
650 return backups;
651 });
652 };
653
654 return checkBackupFile(1);
655 } 49 }
656 }; 50 };
657 51
658 /** 52 /**
659 * Joins subscription's filters to the subscription without any notifications. 53 * Adds a user-defined filter to the most suitable subscription in the list,
660 * @param {Subscription} subscription 54 * creates one if none found.
661 * filter subscription that should be connected to its filters 55 * @param {Filter} filter
662 */ 56 */
663 function addSubscriptionFilters(subscription) 57 FilterStorage.addFilter = function(filter)
664 { 58 {
665 if (!(subscription.url in FilterStorage.knownSubscriptions)) 59 for (let subscription of this.subscriptions)
666 return; 60 if (!subscription.disabled && subscription.indexOfFilter(filter) >= 0)
61 return;
667 62
668 for (let filter of subscription.filters) 63 let subscription = this.getSubscriptionForFilter(filter);
669 filter.subscriptions.push(subscription); 64 try
670 } 65 {
66 if (!subscription)
67 {
68 subscription = Subscription.fromURL(null);
69 subscription.makeDefaultFor(filter);
70 this.addSubscription(subscription);
71 }
72 subscription.insertFilterAt(filter, subscription.filterCount);
73 }
74 finally
75 {
76 if (subscription)
77 subscription.delete();
78 }
79 return true;
80 };
671 81
672 /** 82 /**
673 * Removes subscription's filters from the subscription without any 83 * Removes a user-defined filter from the list
674 * notifications. 84 * @param {Filter} filter
675 * @param {Subscription} subscription filter subscription to be removed
676 */ 85 */
677 function removeSubscriptionFilters(subscription) 86 FilterStorage.removeFilter = function(filter)
678 { 87 {
679 if (!(subscription.url in FilterStorage.knownSubscriptions)) 88 for (let subscription of this.subscriptions)
680 return;
681
682 for (let filter of subscription.filters)
683 { 89 {
684 let i = filter.subscriptions.indexOf(subscription); 90 if (subscription instanceof SpecialSubscription)
685 if (i >= 0)
686 filter.subscriptions.splice(i, 1);
687 }
688 }
689
690 /**
691 * Listener returned by FilterStorage.importData(), parses filter data.
692 * @constructor
693 */
694 function INIParser()
695 {
696 this.fileProperties = this.curObj = {};
697 this.subscriptions = [];
698 this.knownFilters = Object.create(null);
699 this.knownSubscriptions = Object.create(null);
700 }
701 INIParser.prototype =
702 {
703 linesProcessed: 0,
704 subscriptions: null,
705 knownFilters: null,
706 knownSubscriptions: null,
707 wantObj: true,
708 fileProperties: null,
709 curObj: null,
710 curSection: null,
711
712 process(val)
713 {
714 let origKnownFilters = Filter.knownFilters;
715 Filter.knownFilters = this.knownFilters;
716 let origKnownSubscriptions = Subscription.knownSubscriptions;
717 Subscription.knownSubscriptions = this.knownSubscriptions;
718 let match;
719 try
720 { 91 {
721 if (this.wantObj === true && (match = /^(\w+)=(.*)$/.exec(val))) 92 while (true)
722 this.curObj[match[1]] = match[2];
723 else if (val === null || (match = /^\s*\[(.+)\]\s*$/.exec(val)))
724 { 93 {
725 if (this.curObj) 94 let index = subscription.indexOfFilter(filter);
726 { 95 if (index >= 0)
727 // Process current object before going to next section 96 subscription.removeFilterAt(index);
728 switch (this.curSection) 97 else
729 { 98 break;
730 case "filter":
731 if ("text" in this.curObj)
732 Filter.fromObject(this.curObj);
733 break;
734 case "subscription": {
735 let subscription = Subscription.fromObject(this.curObj);
736 if (subscription)
737 this.subscriptions.push(subscription);
738 break;
739 }
740 case "subscription filters":
741 if (this.subscriptions.length)
742 {
743 let subscription = this.subscriptions[
744 this.subscriptions.length - 1
745 ];
746 for (let text of this.curObj)
747 {
748 let filter = Filter.fromText(text);
749 subscription.filters.push(filter);
750 filter.subscriptions.push(subscription);
751 }
752 }
753 break;
754 }
755 }
756
757 if (val === null)
758 return;
759
760 this.curSection = match[1].toLowerCase();
761 switch (this.curSection)
762 {
763 case "filter":
764 case "subscription":
765 this.wantObj = true;
766 this.curObj = {};
767 break;
768 case "subscription filters":
769 this.wantObj = false;
770 this.curObj = [];
771 break;
772 default:
773 this.wantObj = undefined;
774 this.curObj = null;
775 }
776 } 99 }
777 else if (this.wantObj === false && val)
778 this.curObj.push(val.replace(/\\\[/g, "["));
779 }
780 finally
781 {
782 Filter.knownFilters = origKnownFilters;
783 Subscription.knownSubscriptions = origKnownSubscriptions;
784 } 100 }
785 } 101 }
786 }; 102 };
103
104 exports.FilterStorage = FilterStorage;
OLDNEW
« no previous file with comments | « compiled/subscription/UserDefinedSubscription.cpp ('k') | lib/subscriptionClasses.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld