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

Delta Between Two Patch Sets: lib/filterStorage.js

Issue 29375915: Issue 4878 - Start using ESLint for adblockpluscore (Closed)
Left Patch Set: Rebased. Created Feb. 28, 2017, 3:55 p.m.
Right Patch Set: Removed unused imports Created March 15, 2017, 3:11 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « lib/filterNotifier.js ('k') | lib/matcher.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 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 /**
21 * @fileOverview FilterStorage class responsible for managing user's 21 * @fileOverview FilterStorage class responsible for managing user's
22 * subscriptions and filters. 22 * subscriptions and filters.
23 */ 23 */
24 24
25 Cu.import("resource://gre/modules/Services.jsm"); 25 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
26 Cu.import("resource://gre/modules/FileUtils.jsm"); 26 const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
27 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
28 27
29 const {IO} = require("io"); 28 const {IO} = require("io");
30 const {Prefs} = require("prefs"); 29 const {Prefs} = require("prefs");
31 const {Filter, ActiveFilter} = require("filterClasses"); 30 const {Filter, ActiveFilter} = require("filterClasses");
32 const {Subscription, SpecialSubscription, 31 const {Subscription, SpecialSubscription,
33 ExternalSubscription} = require("subscriptionClasses"); 32 ExternalSubscription} = require("subscriptionClasses");
34 const {FilterNotifier} = require("filterNotifier"); 33 const {FilterNotifier} = require("filterNotifier");
35 const {Utils} = require("utils"); 34 const {Utils} = require("utils");
36 35
37 /** 36 /**
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 // Place the file in the data dir 73 // Place the file in the data dir
75 file = IO.resolveFilePath(Prefs.data_directory); 74 file = IO.resolveFilePath(Prefs.data_directory);
76 if (file) 75 if (file)
77 file.append("patterns.ini"); 76 file.append("patterns.ini");
78 } 77 }
79 if (!file) 78 if (!file)
80 { 79 {
81 // Data directory pref misconfigured? Try the default value 80 // Data directory pref misconfigured? Try the default value
82 try 81 try
83 { 82 {
84 file = IO.resolveFilePath( 83 let dir = Services.prefs.getDefaultBranch("extensions.adblockplus.")
85 Services.prefs.getDefaultBranch("extensions.adblockplus." 84 .getCharPref("data_directory");
86 ).getCharPref("data_directory")); 85 file = IO.resolveFilePath(dir);
Wladimir Palant 2017/03/02 14:06:57 Now you have the closing parenthesis from getDefau
kzar 2017/03/08 12:33:44 Whoops, done.
87 if (file) 86 if (file)
88 file.append("patterns.ini"); 87 file.append("patterns.ini");
89 } 88 }
90 catch (e) {} 89 catch (e) {}
91 } 90 }
92 91
93 if (!file) 92 if (!file)
94 { 93 {
95 Cu.reportError("Adblock Plus: Failed to resolve filter file location " + 94 Cu.reportError("Adblock Plus: Failed to resolve filter file location " +
96 "from extensions.adblockplus.patternsfile preference"); 95 "from extensions.adblockplus.patternsfile preference");
(...skipping 26 matching lines...) Expand all
123 /** 122 /**
124 * Map of subscriptions already on the list, by their URL/identifier 123 * Map of subscriptions already on the list, by their URL/identifier
125 * @type {Object} 124 * @type {Object}
126 */ 125 */
127 knownSubscriptions: Object.create(null), 126 knownSubscriptions: Object.create(null),
128 127
129 /** 128 /**
130 * Finds the filter group that a filter should be added to by default. Will 129 * Finds the filter group that a filter should be added to by default. Will
131 * return null if this group doesn't exist yet. 130 * return null if this group doesn't exist yet.
132 * @param {Filter} filter 131 * @param {Filter} filter
133 * @return {SpecialSubscription|null} 132 * @return {?SpecialSubscription}
134 */ 133 */
135 getGroupForFilter(filter) 134 getGroupForFilter(filter)
136 { 135 {
137 let generalSubscription = null; 136 let generalSubscription = null;
138 for (let subscription of FilterStorage.subscriptions) 137 for (let subscription of FilterStorage.subscriptions)
139 { 138 {
140 if (subscription instanceof SpecialSubscription && !subscription.disabled) 139 if (subscription instanceof SpecialSubscription && !subscription.disabled)
141 { 140 {
142 // Always prefer specialized subscriptions 141 // Always prefer specialized subscriptions
143 if (subscription.isDefaultFor(filter)) 142 if (subscription.isDefaultFor(filter))
144 return subscription; 143 return subscription;
145 144
146 // If this is a general subscription - store it as fallback 145 // If this is a general subscription - store it as fallback
147 if (!generalSubscription && 146 if (!generalSubscription &&
148 (!subscription.defaults || !subscription.defaults.length)) 147 (!subscription.defaults || !subscription.defaults.length))
148 {
149 generalSubscription = subscription; 149 generalSubscription = subscription;
150 }
150 } 151 }
151 } 152 }
152 return generalSubscription; 153 return generalSubscription;
153 }, 154 },
154 155
155 /** 156 /**
156 * Adds a filter subscription to the list 157 * Adds a filter subscription to the list
157 * @param {Subscription} subscription filter subscription to be added 158 * @param {Subscription} subscription filter subscription to be added
158 * @param {boolean} silent if true, no listeners will be triggered 159 * @param {boolean} silent if true, no listeners will be triggered
159 * (to be used when filter list is reloaded) 160 * (to be used when filter list is reloaded)
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
234 subscription.oldFilters = subscription.filters; 235 subscription.oldFilters = subscription.filters;
235 subscription.filters = filters; 236 subscription.filters = filters;
236 addSubscriptionFilters(subscription); 237 addSubscriptionFilters(subscription);
237 FilterNotifier.triggerListeners("subscription.updated", subscription); 238 FilterNotifier.triggerListeners("subscription.updated", subscription);
238 delete subscription.oldFilters; 239 delete subscription.oldFilters;
239 }, 240 },
240 241
241 /** 242 /**
242 * Adds a user-defined filter to the list 243 * Adds a user-defined filter to the list
243 * @param {Filter} filter 244 * @param {Filter} filter
244 * @param {SpecialSubscription} [subscription] particular group that the 245 * @param {SpecialSubscription} [subscription]
245 * filter should be added to 246 * particular group that the filter should be added to
246 * @param {number} [position] position within the subscription at which 247 * @param {number} [position]
247 * the filter should be added 248 * position within the subscription at which the filter should be added
248 * @param {boolean} silent if true, no listeners will be triggered (to 249 * @param {boolean} silent
249 * be used when filter list is reloaded) 250 * if true, no listeners will be triggered (to be used when filter list is
Wladimir Palant 2017/03/02 14:06:54 Messy indentation here.
kzar 2017/03/08 12:33:39 Done.
251 * reloaded)
250 */ 252 */
251 addFilter(filter, subscription, position, silent) 253 addFilter(filter, subscription, position, silent)
252 { 254 {
253 if (!subscription) 255 if (!subscription)
254 { 256 {
255 if (filter.subscriptions.some(s => s instanceof SpecialSubscription && 257 if (filter.subscriptions.some(s => s instanceof SpecialSubscription &&
256 !s.disabled)) 258 !s.disabled))
Wladimir Palant 2017/03/02 14:06:57 Wrong indentation, this should be aligned with "s
kzar 2017/03/08 12:33:44 Done.
259 {
257 return; // No need to add 260 return; // No need to add
261 }
258 subscription = FilterStorage.getGroupForFilter(filter); 262 subscription = FilterStorage.getGroupForFilter(filter);
259 } 263 }
260 if (!subscription) 264 if (!subscription)
261 { 265 {
262 // No group for this filter exists, create one 266 // No group for this filter exists, create one
263 subscription = SpecialSubscription.createForFilter(filter); 267 subscription = SpecialSubscription.createForFilter(filter);
264 this.addSubscription(subscription); 268 this.addSubscription(subscription);
265 return; 269 return;
266 } 270 }
267 271
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
337 * @param {Filter} filter 341 * @param {Filter} filter
338 * @param {SpecialSubscription} subscription filter group where the filter is 342 * @param {SpecialSubscription} subscription filter group where the filter is
339 * located 343 * located
340 * @param {number} oldPosition current position of the filter 344 * @param {number} oldPosition current position of the filter
341 * @param {number} newPosition new position of the filter 345 * @param {number} newPosition new position of the filter
342 */ 346 */
343 moveFilter(filter, subscription, oldPosition, newPosition) 347 moveFilter(filter, subscription, oldPosition, newPosition)
344 { 348 {
345 if (!(subscription instanceof SpecialSubscription) || 349 if (!(subscription instanceof SpecialSubscription) ||
346 subscription.filters[oldPosition] != filter) 350 subscription.filters[oldPosition] != filter)
347 return; 351 {
352 return;
353 }
348 354
349 newPosition = Math.min(Math.max(newPosition, 0), 355 newPosition = Math.min(Math.max(newPosition, 0),
350 subscription.filters.length - 1); 356 subscription.filters.length - 1);
351 if (oldPosition == newPosition) 357 if (oldPosition == newPosition)
352 return; 358 return;
353 359
354 subscription.filters.splice(oldPosition, 1); 360 subscription.filters.splice(oldPosition, 1);
355 subscription.filters.splice(newPosition, 0, filter); 361 subscription.filters.splice(newPosition, 0, filter);
356 FilterNotifier.triggerListeners("filter.moved", filter, subscription, 362 FilterNotifier.triggerListeners("filter.moved", filter, subscription,
357 oldPosition, newPosition); 363 oldPosition, newPosition);
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
396 * Loads all subscriptions from the disk 402 * Loads all subscriptions from the disk
397 * @param {nsIFile} [sourceFile] File to read from 403 * @param {nsIFile} [sourceFile] File to read from
398 */ 404 */
399 loadFromDisk(sourceFile) 405 loadFromDisk(sourceFile)
400 { 406 {
401 if (this._loading) 407 if (this._loading)
402 return; 408 return;
403 409
404 this._loading = true; 410 this._loading = true;
405 411
406 let readFile = function(currentSourceFile, backupIndex) 412 let readFile = (currentSourceFile, backupIndex) =>
Wladimir Palant 2017/03/02 14:06:56 Why hasn't this been converted to an arrow functio
kzar 2017/03/08 12:33:43 Done.
407 { 413 {
408 let parser = new INIParser(); 414 let parser = new INIParser();
409 IO.readFromFile(currentSourceFile, parser, readFromFileException => 415 IO.readFromFile(currentSourceFile, parser, readFromFileException =>
Wladimir Palant 2017/03/02 14:06:58 Ok, having to name every exception is ridiculous..
kzar 2017/03/08 12:33:42 Acknowledged.
410 { 416 {
411 if (!readFromFileException && parser.subscriptions.length == 0) 417 if (!readFromFileException && parser.subscriptions.length == 0)
412 { 418 {
413 // No filter subscriptions in the file, this isn't right. 419 // No filter subscriptions in the file, this isn't right.
414 readFromFileException = new Error("No data in the file"); 420 readFromFileException = new Error("No data in the file");
415 } 421 }
416 422
417 if (readFromFileException) 423 if (readFromFileException)
418 Cu.reportError(readFromFileException); 424 Cu.reportError(readFromFileException);
419 425
(...skipping 17 matching lines...) Expand all
437 if (!statFileException && statData.exists) 443 if (!statFileException && statData.exists)
438 readFile(currentSourceFile, backupIndex); 444 readFile(currentSourceFile, backupIndex);
439 else 445 else
440 doneReading(parser); 446 doneReading(parser);
441 }); 447 });
442 return; 448 return;
443 } 449 }
444 } 450 }
445 doneReading(parser); 451 doneReading(parser);
446 }); 452 });
447 }.bind(this); 453 };
448 454
449 let doneReading = function(parser) 455 let doneReading = parser =>
Wladimir Palant 2017/03/02 14:06:56 Why hasn't this been converted to an arrow functio
kzar 2017/03/08 12:33:42 Done.
450 { 456 {
451 // Old special groups might have been converted, remove them if 457 // Old special groups might have been converted, remove them if
452 // they are empty 458 // they are empty
453 let specialMap = {"~il~": true, "~wl~": true, "~fl~": true, "~eh~": true}; 459 let specialMap = {"~il~": true, "~wl~": true, "~fl~": true, "~eh~": true};
454 let knownSubscriptions = Object.create(null); 460 let knownSubscriptions = Object.create(null);
455 for (let i = 0; i < parser.subscriptions.length; i++) 461 for (let i = 0; i < parser.subscriptions.length; i++)
456 { 462 {
457 let subscription = parser.subscriptions[i]; 463 let subscription = parser.subscriptions[i];
458 if (subscription instanceof SpecialSubscription && 464 if (subscription instanceof SpecialSubscription &&
459 subscription.filters.length == 0 && subscription.url in specialMap) 465 subscription.filters.length == 0 && subscription.url in specialMap)
466 {
460 parser.subscriptions.splice(i--, 1); 467 parser.subscriptions.splice(i--, 1);
468 }
461 else 469 else
462 knownSubscriptions[subscription.url] = subscription; 470 knownSubscriptions[subscription.url] = subscription;
463 } 471 }
464 472
465 this.fileProperties = parser.fileProperties; 473 this.fileProperties = parser.fileProperties;
466 this.subscriptions = parser.subscriptions; 474 this.subscriptions = parser.subscriptions;
467 this.knownSubscriptions = knownSubscriptions; 475 this.knownSubscriptions = knownSubscriptions;
468 Filter.knownFilters = parser.knownFilters; 476 Filter.knownFilters = parser.knownFilters;
469 Subscription.knownSubscriptions = parser.knownSubscriptions; 477 Subscription.knownSubscriptions = parser.knownSubscriptions;
470 478
471 if (parser.userFilters) 479 if (parser.userFilters)
472 { 480 {
473 for (let i = 0; i < parser.userFilters.length; i++) 481 for (let i = 0; i < parser.userFilters.length; i++)
474 { 482 {
475 let filter = Filter.fromText(parser.userFilters[i]); 483 let filter = Filter.fromText(parser.userFilters[i]);
476 this.addFilter(filter, null, undefined, true); 484 this.addFilter(filter, null, undefined, true);
477 } 485 }
478 } 486 }
479 487
480 this._loading = false; 488 this._loading = false;
481 FilterNotifier.triggerListeners("load"); 489 FilterNotifier.triggerListeners("load");
482 490
483 if (sourceFile != this.sourceFile) 491 if (sourceFile != this.sourceFile)
484 this.saveToDisk(); 492 this.saveToDisk();
485 }.bind(this); 493 };
486 494
487 let explicitFile; 495 let explicitFile;
488 if (sourceFile) 496 if (sourceFile)
489 { 497 {
490 explicitFile = true; 498 explicitFile = true;
491 readFile(sourceFile, 0); 499 readFile(sourceFile, 0);
492 } 500 }
493 else 501 else
494 { 502 {
495 explicitFile = false; 503 explicitFile = false;
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
594 } 602 }
595 603
596 // Make sure the file's parent directory exists 604 // Make sure the file's parent directory exists
597 try 605 try
598 { 606 {
599 targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, 607 targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE,
600 FileUtils.PERMS_DIRECTORY); 608 FileUtils.PERMS_DIRECTORY);
601 } 609 }
602 catch (e) {} 610 catch (e) {}
603 611
604 let writeFilters = function() 612 let writeFilters = () =>
Wladimir Palant 2017/03/02 14:06:54 Why hasn't this been converted to an arrow functio
kzar 2017/03/08 12:33:41 Done.
605 { 613 {
606 IO.writeToFile(targetFile, this._generateFilterData(subscriptions), e => 614 IO.writeToFile(targetFile, this._generateFilterData(subscriptions), e =>
607 { 615 {
608 if (!explicitFile) 616 if (!explicitFile)
609 this._saving = false; 617 this._saving = false;
610 618
611 if (e) 619 if (e)
612 Cu.reportError(e); 620 Cu.reportError(e);
613 621
614 if (!explicitFile && this._needsSave) 622 if (!explicitFile && this._needsSave)
615 { 623 {
616 this._needsSave = false; 624 this._needsSave = false;
617 this.saveToDisk(); 625 this.saveToDisk();
618 } 626 }
619 else 627 else
620 FilterNotifier.triggerListeners("save"); 628 FilterNotifier.triggerListeners("save");
621 }); 629 });
622 }.bind(this); 630 };
623 631
624 let checkBackupRequired = function(callbackNotRequired, callbackRequired) 632 let checkBackupRequired = (callbackNotRequired, callbackRequired) =>
Wladimir Palant 2017/03/02 14:06:57 Why hasn't this been converted to an arrow functio
kzar 2017/03/08 12:33:40 Done.
625 { 633 {
626 if (explicitFile || Prefs.patternsbackups <= 0) 634 if (explicitFile || Prefs.patternsbackups <= 0)
627 callbackNotRequired(); 635 callbackNotRequired();
628 else 636 else
629 { 637 {
630 IO.statFile(targetFile, (statFileException, statData) => 638 IO.statFile(targetFile, (statFileException, statData) =>
631 { 639 {
632 if (statFileException || !statData.exists) 640 if (statFileException || !statData.exists)
633 callbackNotRequired(); 641 callbackNotRequired();
634 else 642 else
635 { 643 {
636 let [, part1, part2] = /^(.*)(\.\w+)$/.exec(targetFile.leafName) || 644 let [, part1, part2] = /^(.*)(\.\w+)$/.exec(targetFile.leafName) ||
637 [null, targetFile.leafName, ""]; 645 [null, targetFile.leafName, ""];
638 let newestBackup = targetFile.clone(); 646 let newestBackup = targetFile.clone();
639 newestBackup.leafName = part1 + "-backup1" + part2; 647 newestBackup.leafName = part1 + "-backup1" + part2;
640 IO.statFile( 648 IO.statFile(
641 newestBackup, 649 newestBackup,
642 (statBackupFileException, statBackupData) => 650 (statBackupFileException, statBackupData) =>
643 { 651 {
644 if (!statBackupFileException && (!statBackupData.exists || 652 if (!statBackupFileException && (!statBackupData.exists ||
645 (Date.now() - statBackupData.lastModified) / 653 (Date.now() - statBackupData.lastModified) /
646 3600000 >= Prefs.patternsbackupinterval)) 654 3600000 >= Prefs.patternsbackupinterval))
655 {
647 callbackRequired(part1, part2); 656 callbackRequired(part1, part2);
657 }
648 else 658 else
649 callbackNotRequired(); 659 callbackNotRequired();
650 } 660 }
651 ); 661 );
652 } 662 }
653 }); 663 });
654 } 664 }
655 }; 665 };
656 666
657 let removeLastBackup = function(part1, part2) 667 let removeLastBackup = (part1, part2) =>
Wladimir Palant 2017/03/02 14:06:55 Why hasn't this been converted to an arrow functio
kzar 2017/03/08 12:33:38 Done.
658 { 668 {
659 let file = targetFile.clone(); 669 let file = targetFile.clone();
660 file.leafName = part1 + "-backup" + Prefs.patternsbackups + part2; 670 file.leafName = part1 + "-backup" + Prefs.patternsbackups + part2;
661 IO.removeFile( 671 IO.removeFile(
662 file, e => renameBackup(part1, part2, Prefs.patternsbackups - 1) 672 file, e => renameBackup(part1, part2, Prefs.patternsbackups - 1)
663 ); 673 );
664 }; 674 };
665 675
666 let renameBackup = function(part1, part2, index) 676 let renameBackup = (part1, part2, index) =>
Wladimir Palant 2017/03/02 14:06:55 Why hasn't this been converted to an arrow functio
kzar 2017/03/08 12:33:43 Done.
667 { 677 {
668 if (index > 0) 678 if (index > 0)
669 { 679 {
670 let fromFile = targetFile.clone(); 680 let fromFile = targetFile.clone();
671 fromFile.leafName = part1 + "-backup" + index + part2; 681 fromFile.leafName = part1 + "-backup" + index + part2;
672 682
673 let toName = part1 + "-backup" + (index + 1) + part2; 683 let toName = part1 + "-backup" + (index + 1) + part2;
674 684
675 IO.renameFile(fromFile, toName, e => renameBackup(part1, part2, 685 IO.renameFile(fromFile, toName, e => renameBackup(part1, part2,
676 index - 1)); 686 index - 1));
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
713 result.push(file); 723 result.push(file);
714 else 724 else
715 break; 725 break;
716 } 726 }
717 return result; 727 return result;
718 } 728 }
719 }; 729 };
720 730
721 /** 731 /**
722 * Joins subscription's filters to the subscription without any notifications. 732 * Joins subscription's filters to the subscription without any notifications.
723 * @param {Subscription} subscription filter subscription that should be 733 * @param {Subscription} subscription
724 * connected to its filters 734 * filter subscription that should be connected to its filters
Wladimir Palant 2017/03/02 14:06:54 Messy indentation here.
kzar 2017/03/08 12:33:41 Done.
725 */ 735 */
726 function addSubscriptionFilters(subscription) 736 function addSubscriptionFilters(subscription)
727 { 737 {
728 if (!(subscription.url in FilterStorage.knownSubscriptions)) 738 if (!(subscription.url in FilterStorage.knownSubscriptions))
729 return; 739 return;
730 740
731 for (let filter of subscription.filters) 741 for (let filter of subscription.filters)
732 filter.subscriptions.push(subscription); 742 filter.subscriptions.push(subscription);
733 } 743 }
734 744
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
855 Subscription.knownSubscriptions = origKnownSubscriptions; 865 Subscription.knownSubscriptions = origKnownSubscriptions;
856 } 866 }
857 867
858 // Allow events to be processed every now and then. 868 // Allow events to be processed every now and then.
859 // Note: IO.readFromFile() will deal with the potential reentrance here. 869 // Note: IO.readFromFile() will deal with the potential reentrance here.
860 this.linesProcessed++; 870 this.linesProcessed++;
861 if (this.linesProcessed % 1000 == 0) 871 if (this.linesProcessed % 1000 == 0)
862 return Utils.yield(); 872 return Utils.yield();
863 } 873 }
864 }; 874 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld