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

Delta Between Two Patch Sets: lib/elemHide.js

Issue 4875173639487488: Optmize filter list in elemHide.js for memory usage (Closed)
Left Patch Set: Created April 4, 2014, 11:34 a.m.
Right Patch Set: So I think I found a good solution. In filterListener.js we already have the concept of flushing el… Created May 5, 2014, 3:47 p.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 | « no previous file | lib/filterListener.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 <http://adblockplus.org/>, 2 * This file is part of Adblock Plus <http://adblockplus.org/>,
3 * Copyright (C) 2006-2014 Eyeo GmbH 3 * Copyright (C) 2006-2014 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
(...skipping 12 matching lines...) Expand all
23 23
24 let {Utils} = require("utils"); 24 let {Utils} = require("utils");
25 let {IO} = require("io"); 25 let {IO} = require("io");
26 let {Prefs} = require("prefs"); 26 let {Prefs} = require("prefs");
27 let {ElemHideException} = require("filterClasses"); 27 let {ElemHideException} = require("filterClasses");
28 let {FilterNotifier} = require("filterNotifier"); 28 let {FilterNotifier} = require("filterNotifier");
29 let {AboutHandler} = require("elemHideHitRegistration"); 29 let {AboutHandler} = require("elemHideHitRegistration");
30 let {TimeLine} = require("timeline"); 30 let {TimeLine} = require("timeline");
31 31
32 /** 32 /**
33 * List of all filters. Index is used as key. 33 * Array of filters, index as "key".
34 * @type Array
35 */
36 let filters = [];
37 /**
38 * Lookup table, keys of the filters by filter text.
39 * Can be null.
34 * @type Object 40 * @type Object
35 */ 41 */
36 let filters = []; 42 let keyByFilter = new Map();
37 43
38 /** 44 /**
39 * Lookup table, keys are known element hiding exceptions 45 * Lookup table, keys are known element hiding exceptions
40 * @type Object 46 * @type Object
41 */ 47 */
42 let knownExceptions = {__proto__: null}; 48 let knownExceptions = {__proto__: null};
43 49
44 /** 50 /**
45 * Lookup table, lists of element hiding exceptions by selector 51 * Lookup table, lists of element hiding exceptions by selector
46 * @type Object 52 * @type Object
47 */ 53 */
48 let exceptions = {__proto__: null}; 54 let exceptions = {__proto__: null};
49 55
50 /** 56 /**
51 * Currently applied stylesheet URL 57 * Currently applied stylesheet URL
52 * @type nsIURI 58 * @type nsIURI
53 */ 59 */
54 let styleURL = null; 60 let styleURL = null;
61
62 /**
63 * Filters removed since last compact operation
64 * @type number
65 */
66 let filtersRemoved = 0;
55 67
56 /** 68 /**
57 * Element hiding component 69 * Element hiding component
58 * @class 70 * @class
59 */ 71 */
60 let ElemHide = exports.ElemHide = 72 let ElemHide = exports.ElemHide =
61 { 73 {
62 /** 74 /**
63 * Indicates whether filters have been added or removed since the last apply() call. 75 * Indicates whether filters have been added or removed since the last apply() call.
64 * @type Boolean 76 * @type Boolean
(...skipping 30 matching lines...) Expand all
95 TimeLine.log("done determining stylesheet URL"); 107 TimeLine.log("done determining stylesheet URL");
96 108
97 TimeLine.leave("ElemHide.init() done"); 109 TimeLine.leave("ElemHide.init() done");
98 }, 110 },
99 111
100 /** 112 /**
101 * Removes all known filters 113 * Removes all known filters
102 */ 114 */
103 clear: function() 115 clear: function()
104 { 116 {
117 dump("clear\n");
118 keyByFilter = null;
105 filters = []; 119 filters = [];
106 knownExceptions = {__proto__: null}; 120 knownExceptions = {__proto__: null};
107 exceptions = {__proto__: null}; 121 exceptions = {__proto__: null};
108 ElemHide.isDirty = false; 122 ElemHide.isDirty = false;
109 ElemHide.unapply(); 123 ElemHide.unapply();
110 }, 124 },
111 125
112 /** 126 /**
113 * Add a new element hiding filter 127 * Add a new element hiding filter
114 * @param {ElemHideFilter} filter 128 * @param {ElemHideFilter} filter
115 */ 129 */
116 add: function(filter) 130 add: function(filter)
117 { 131 {
118 if (filter instanceof ElemHideException) 132 if (filter instanceof ElemHideException)
119 { 133 {
120 if (filter.text in knownExceptions) 134 if (filter.text in knownExceptions)
121 return; 135 return;
122 136
123 let selector = filter.selector; 137 let selector = filter.selector;
124 if (!(selector in exceptions)) 138 if (!(selector in exceptions))
125 exceptions[selector] = []; 139 exceptions[selector] = [];
126 exceptions[selector].push(filter); 140 exceptions[selector].push(filter);
127 knownExceptions[filter.text] = true; 141 knownExceptions[filter.text] = true;
128 } 142 }
129 else 143 else
130 { 144 {
145 this._ensureFilterMap();
146
147 if (keyByFilter.has(filter.text))
148 return;
149
131 filters.push(filter); 150 filters.push(filter);
132 // XXX: Maybe check for duplicate filters? 151 keyByFilter.set(filter.text, filters.length - 1);
133 // That would require searching through all filters, 152
134 // and in almost all cases there won't be any duplicates ...
135 ElemHide.isDirty = true; 153 ElemHide.isDirty = true;
136 } 154 }
137 }, 155 },
138 156
139 /** 157 /**
140 * Removes an element hiding filter 158 * Removes an element hiding filter
141 * @param {ElemHideFilter} filter 159 * @param {ElemHideFilter} filter
142 */ 160 */
143 remove: function(filter) 161 remove: function(filter)
144 { 162 {
145 if (filter instanceof ElemHideException) 163 if (filter instanceof ElemHideException)
146 { 164 {
147 if (!(filter.text in knownExceptions)) 165 if (!(filter.text in knownExceptions))
148 return; 166 return;
149 167
150 let list = exceptions[filter.selector]; 168 let list = exceptions[filter.selector];
151 let index = list.indexOf(filter); 169 let index = list.indexOf(filter);
152 if (index >= 0) 170 if (index >= 0)
153 list.splice(index, 1); 171 list.splice(index, 1);
154 delete knownExceptions[filter.text]; 172 delete knownExceptions[filter.text];
155 } 173 }
156 else 174 else
157 { 175 {
158 let index = filters.indexOf(filter); 176 this._ensureFilterMap();
159 if (index < 0) 177
178 let key = keyByFilter.get(filter.text);
179 if (key === undefined)
160 return; 180 return;
161 filters[index] = undefined; // Indicates deleted. 181
Wladimir Palant 2014/04/08 11:24:53 This won't work correctly if the filter is duplica
162 182 keyByFilter.delete(filter.text);
183 filters[key] = null;
163 ElemHide.isDirty = true; 184 ElemHide.isDirty = true;
164 } 185 filtersRemoved += 1;
186 }
187 },
188
189 /**
190 * Clean up and compact memory after filters
191 * are removed.
192 */
193 compact: function()
194 {
195 if (filtersRemoved == 0) {
196 return;
197 }
198
199 if (filtersRemoved == 1) {
200 for (let i = 0; i < filters; i++) {
201 if (filters[i] === null)
202 filters.splice(i, 1);
203 }
204 } else {
205 let newList = [];
206 for (let filter in filters) {
207 if (filter)
208 newList.push(filter);
209 }
210 filters = newList;
211 }
212
213 filtersRemoved = 0;
214 },
215
216 /**
217 * Ensure that we have a map of filter text to key.
218 * Might have to recreate it from the array of filters.
219 */
220 _ensureFilterMap: function()
221 {
222 if (keyByFilter)
223 return;
224
225 keyByFilter = new Map();
226 for (let i = 0; i < filters.length; i++)
227 keyByFilter.set(filters[i].text, i);
165 }, 228 },
166 229
167 /** 230 /**
168 * Checks whether an exception rule is registered for a filter on a particular 231 * Checks whether an exception rule is registered for a filter on a particular
169 * domain. 232 * domain.
170 */ 233 */
171 getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideE xception*/ 234 getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideE xception*/
172 { 235 {
173 let selector = filter.selector; 236 let selector = filter.selector;
174 if (!(filter.selector in exceptions)) 237 if (!(filter.selector in exceptions))
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
227 else if (!Prefs.enabled && ElemHide.applied) 290 else if (!Prefs.enabled && ElemHide.applied)
228 { 291 {
229 ElemHide.unapply(); 292 ElemHide.unapply();
230 TimeLine.log("ElemHide.unapply() finished"); 293 TimeLine.log("ElemHide.unapply() finished");
231 } 294 }
232 295
233 TimeLine.leave("ElemHide.apply() done (no file changes)"); 296 TimeLine.leave("ElemHide.apply() done (no file changes)");
234 return; 297 return;
235 } 298 }
236 299
237 IO.writeToFile(styleURL.file, false, this._generateCSSContent(), function(e) 300 IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e)
238 { 301 {
239 TimeLine.enter("ElemHide.apply() write callback"); 302 TimeLine.enter("ElemHide.apply() write callback");
240 this._applying = false; 303 this._applying = false;
241 304
242 if (e && e.result == Cr.NS_ERROR_NOT_AVAILABLE) 305 // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that
243 IO.removeFile(styleURL.file, function(e2) {}); 306 // there are no filters. If that exception is passed through XPCOM we will
307 // see a proper exception here, otherwise a number.
308 let noFilters = (e == Cr.NS_ERROR_NOT_AVAILABLE || (e && e.result == Cr.NS _ERROR_NOT_AVAILABLE));
309 if (noFilters)
310 {
311 e = null;
312 IO.removeFile(styleURL.file, function(e) {});
313 }
244 else if (e) 314 else if (e)
245 Cu.reportError(e); 315 Cu.reportError(e);
246 316
247 if (this._needsApply) 317 if (this._needsApply)
248 { 318 {
249 this._needsApply = false; 319 this._needsApply = false;
250 this.apply(); 320 this.apply();
251 } 321 }
252 else if (!e || e.result == Cr.NS_ERROR_NOT_AVAILABLE) 322 else if (!e)
253 { 323 {
254 ElemHide.isDirty = false; 324 ElemHide.isDirty = false;
255 325
256 ElemHide.unapply(); 326 ElemHide.unapply();
257 TimeLine.log("ElemHide.unapply() finished"); 327 TimeLine.log("ElemHide.unapply() finished");
258 328
259 if (!e) 329 if (!noFilters)
260 { 330 {
261 try 331 try
262 { 332 {
263 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetSe rvice.USER_SHEET); 333 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetSe rvice.USER_SHEET);
264 ElemHide.applied = true; 334 ElemHide.applied = true;
265 } 335 }
266 catch (e) 336 catch (e)
267 { 337 {
268 Cu.reportError(e); 338 Cu.reportError(e);
269 } 339 }
270 TimeLine.log("Applying stylesheet finished"); 340 TimeLine.log("Applying stylesheet finished");
271 } 341 }
272 342
273 FilterNotifier.triggerListeners("elemhideupdate"); 343 FilterNotifier.triggerListeners("elemhideupdate");
274 } 344 }
275 TimeLine.leave("ElemHide.apply() write callback done"); 345 TimeLine.leave("ElemHide.apply() write callback done");
276 }.bind(this), "ElemHideWrite"); 346 }.bind(this), "ElemHideWrite");
277 347
278 this._applying = true; 348 this._applying = true;
279 349
280 TimeLine.leave("ElemHide.apply() done", "ElemHideWrite"); 350 TimeLine.leave("ElemHide.apply() done", "ElemHideWrite");
281 }, 351 },
282 352
283 _generateCSSContent: function() 353 _generateCSSContent: function()
284 { 354 {
355 // We don't need keyByFilter anymore now, if we need it again,
356 // e.g. because of a subscription change, we will regenerate it.
357 keyByFilter = null;
358
285 // Grouping selectors by domains 359 // Grouping selectors by domains
286 TimeLine.log("start grouping selectors"); 360 TimeLine.log("start grouping selectors");
287 let domains = {__proto__: null}; 361 let domains = {__proto__: null};
288 let hasFilters = false; 362 let hasFilters = false;
289 let seen = []; 363 for (let key = 0; key < filters.length; key++)
290 for (let i = 0; i < filters.length; i++) 364 {
291 { 365 let filter = filters[key];
292 let filter = filters[i];
293 if (!filter)
294 continue;
295
296 // Lazily de-duplicate filters.
297 if (seen.indexOf(filter) >= 0) {
tschuster 2014/04/04 13:17:45 I am afraid this piece of code is causing noticeab
Wladimir Palant 2014/04/08 11:24:53 In fact, we are very likely to see duplicates - th
298 filters[i] = undefined;
299 continue;
300 } else {
301 seen.push(filter);
302 }
303
304 let domain = filter.selectorDomain || ""; 366 let domain = filter.selectorDomain || "";
305 367
306 let list; 368 let list;
307 if (domain in domains) 369 if (domain in domains)
308 list = domains[domain]; 370 list = domains[domain];
309 else 371 else
310 { 372 {
311 list = {__proto__: null}; 373 list = {__proto__: null};
312 domains[domain] = list; 374 domains[domain] = list;
313 } 375 }
314 list[filter.selector] = i; 376 list[filter.selector] = key;
315 hasFilters = true; 377 hasFilters = true;
316 } 378 }
317 TimeLine.log("done grouping selectors"); 379 TimeLine.log("done grouping selectors");
318 380
319 if (!hasFilters) 381 if (!hasFilters)
320 throw Cr.NS_ERROR_NOT_AVAILABLE; 382 throw Cr.NS_ERROR_NOT_AVAILABLE;
321 383
322 function escapeChar(match) 384 function escapeChar(match)
323 { 385 {
324 return "\\" + match.charCodeAt(0).toString(16) + " "; 386 return "\\" + match.charCodeAt(0).toString(16) + " ";
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
370 * Retrieves the currently applied stylesheet URL 432 * Retrieves the currently applied stylesheet URL
371 * @type String 433 * @type String
372 */ 434 */
373 get styleURL() ElemHide.applied ? styleURL.spec : null, 435 get styleURL() ElemHide.applied ? styleURL.spec : null,
374 436
375 /** 437 /**
376 * Retrieves an element hiding filter by the corresponding protocol key 438 * Retrieves an element hiding filter by the corresponding protocol key
377 */ 439 */
378 getFilterByKey: function(/**String*/ key) /**Filter*/ 440 getFilterByKey: function(/**String*/ key) /**Filter*/
379 { 441 {
380 return filters[key] || null; 442 return (key < filters.length ? filters[key] : null);
381 }, 443 },
382 444
383 /** 445 /**
384 * Returns a list of all selectors active on a particular domain (currently 446 * Returns a list of all selectors active on a particular domain (currently
385 * used only in Chrome). 447 * used only in Chrome).
386 */ 448 */
387 getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) 449 getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly)
388 { 450 {
389 let result = []; 451 let result = [];
390 for (let i = 0; i < filters.length; i++) 452 for (let filter of filters)
391 { 453 {
392 let filter = filters[i]; 454 if (!filter)
455 continue
456
393 if (specificOnly && (!filter.domains || filter.domains[""])) 457 if (specificOnly && (!filter.domains || filter.domains[""]))
394 continue; 458 continue;
395 459
396 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) 460 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain))
397 result.push(filter.selector); 461 result.push(filter.selector);
398 } 462 }
399 return result; 463 return result;
400 } 464 }
401 }; 465 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld