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

Delta Between Two Patch Sets: lib/elemHide.js

Issue 29342830: Issue 4057 - Further speedup ElemHide.getSelectorsforDomain (Closed)
Left Patch Set: Take care to remove filters from lookups Created May 20, 2016, 10:45 a.m.
Right Patch Set: Addressed nits Created May 25, 2016, 10:50 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 | « no previous file | test/elemHide.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
(...skipping 13 matching lines...) Expand all
24 var {Utils} = require("utils"); 24 var {Utils} = require("utils");
25 var {IO} = require("io"); 25 var {IO} = require("io");
26 var {Prefs} = require("prefs"); 26 var {Prefs} = require("prefs");
27 var {ElemHideException} = require("filterClasses"); 27 var {ElemHideException} = require("filterClasses");
28 var {FilterNotifier} = require("filterNotifier"); 28 var {FilterNotifier} = require("filterNotifier");
29 29
30 /** 30 /**
31 * Lookup table, filters by their associated key 31 * Lookup table, filters by their associated key
32 * @type Object 32 * @type Object
33 */ 33 */
34 var filterByKey = Object.create(null); 34 var filterByKey = [];
35 35
36 /** 36 /**
37 * Lookup table, keys of the filters by filter text 37 * Lookup table, keys of the filters by filter text
38 * @type Object 38 * @type Object
39 */ 39 */
40 var keyByFilter = Object.create(null); 40 var keyByFilter = Object.create(null);
41 41
42 /** 42 /**
43 * Indicates whether we are using getSelectorsByDomain and maintaining the 43 * Indicates whether we are using the getSelectorsForDomain function and
44 * required lookup tables. (Will be false for Firefox) 44 * therefore mainting the required filtersByDomain, filtersBySelector and
45 * unconditionalSelectors lookups. (Will be false for Firefox)
45 * @type Boolean 46 * @type Boolean
46 */ 47 */
47 var usingGetSelectorsByDomain = !("nsIStyleSheetService" in Ci); 48 var usingGetSelectorsForDomain = !("nsIStyleSheetService" in Ci);
48 49
49 /** 50 /**
50 * Lookup table, filter selector by filter ID. 51 * Nested lookup table, filter (or false if inactive) by filter key by domain.
51 */ 52 * (Only contains filters that aren't unconditionally matched for all domains.)
52 var selectorByFilterId = []; 53 * @type Object
53 54 */
54 /** 55 var filtersByDomain = Object.create(null);
55 * Lookup table, active filter IDs by domain. 56
56 */ 57 /**
57 var activeFilterIdsByDomain = Object.create(null); 58 * Lookup table, filters by selector. (Only contains filters that have a
58 59 * selector that is unconditionally matched for all domains.)
59 /** 60 */
60 * Lookup table, inactive filter IDs by domain. 61 var filtersBySelector = Object.create(null);
61 */ 62
62 var inactiveFilterIdsByDomain = Object.create(null); 63 /**
63 64 * This array caches the keys of filtersBySelector table (selectors which
64 /** 65 * unconditionally apply on all domains). It will be null if the cache needs to
65 * Lookup table, filter ID by filter text. 66 * be rebuilt.
66 */ 67 */
67 var filterIdByFilterText = Object.create(null); 68 var unconditionalSelectors = null;
68 69
69 /** 70 /**
70 * Object to be used instead when a filter has a blank domains property. 71 * Object to be used instead when a filter has a blank domains property.
71 */ 72 */
72 var defaultDomains = Object.create(null); 73 var defaultDomains = Object.create(null);
73 defaultDomains[""] = true; 74 defaultDomains[""] = true;
74 75
75 /** 76 /**
76 * Lookup table, keys are known element hiding exceptions 77 * Lookup table, keys are known element hiding exceptions
77 * @type Object 78 * @type Object
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
123 let styleFile = IO.resolveFilePath(Prefs.data_directory); 124 let styleFile = IO.resolveFilePath(Prefs.data_directory);
124 styleFile.append("elemhide.css"); 125 styleFile.append("elemhide.css");
125 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL); 126 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL);
126 }, 127 },
127 128
128 /** 129 /**
129 * Removes all known filters 130 * Removes all known filters
130 */ 131 */
131 clear: function() 132 clear: function()
132 { 133 {
133 filterByKey = Object.create(null); 134 filterByKey = [];
134 keyByFilter = Object.create(null); 135 keyByFilter = Object.create(null);
135 selectorByFilterId = []; 136 filtersByDomain = Object.create(null);
136 activeFilterIdsByDomain = Object.create(null); 137 filtersBySelector = Object.create(null);
137 inactiveFilterIdsByDomain = Object.create(null); 138 unconditionalSelectors = null;
138 filterIdByFilterText = Object.create(null);
139 knownExceptions = Object.create(null); 139 knownExceptions = Object.create(null);
140 exceptions = Object.create(null); 140 exceptions = Object.create(null);
141 ElemHide.isDirty = false; 141 ElemHide.isDirty = false;
142 ElemHide.unapply(); 142 ElemHide.unapply();
143 }, 143 },
144 144
145 _addToFiltersByDomain: function(filter)
146 {
147 let key = keyByFilter[filter.text];
148 let domains = filter.domains || defaultDomains;
149 for (let domain in domains)
150 {
151 let filters = filtersByDomain[domain];
152 if (!filters)
153 filters = filtersByDomain[domain] = Object.create(null);
154
155 if (domains[domain])
156 filters[key] = filter;
157 else
158 filters[key] = false;
159 }
160 },
161
145 /** 162 /**
146 * Add a new element hiding filter 163 * Add a new element hiding filter
147 * @param {ElemHideFilter} filter 164 * @param {ElemHideFilter} filter
148 */ 165 */
149 add: function(filter) 166 add: function(filter)
150 { 167 {
151 if (filter instanceof ElemHideException) 168 if (filter instanceof ElemHideException)
152 { 169 {
153 if (filter.text in knownExceptions) 170 if (filter.text in knownExceptions)
154 return; 171 return;
155 172
156 let selector = filter.selector; 173 let selector = filter.selector;
157 if (!(selector in exceptions)) 174 if (!(selector in exceptions))
158 exceptions[selector] = []; 175 exceptions[selector] = [];
159 exceptions[selector].push(filter); 176 exceptions[selector].push(filter);
177
178 if (usingGetSelectorsForDomain)
179 {
180 // If this is the first exception for a previously unconditionally
181 // applied element hiding selector we need to take care to update the
182 // lookups.
183 let unconditionalFilters = filtersBySelector[selector];
184 if (unconditionalFilters)
185 {
186 for (let f of unconditionalFilters)
187 this._addToFiltersByDomain(f);
188 delete filtersBySelector[selector];
189 unconditionalSelectors = null;
190 }
191 }
192
160 knownExceptions[filter.text] = true; 193 knownExceptions[filter.text] = true;
161 } 194 }
162 else 195 else
163 { 196 {
164 if (filter.text in keyByFilter) 197 if (filter.text in keyByFilter)
165 return; 198 return;
166 199
167 let key; 200 let key = filterByKey.push(filter) - 1;
168 do {
169 key = Math.random().toFixed(15).substr(5);
170 } while (key in filterByKey);
171
172 filterByKey[key] = filter;
173 keyByFilter[filter.text] = key; 201 keyByFilter[filter.text] = key;
174 202
175 if (usingGetSelectorsByDomain) 203 if (usingGetSelectorsForDomain)
176 { 204 {
177 let filterId = filterIdByFilterText[filter.text]; 205 if (!(filter.domains || filter.selector in exceptions))
178 if (filterId == undefined) 206 {
Wladimir Palant 2016/05/20 12:33:27 That's one of the few cases where you need ===, be
Sebastian Noack 2016/05/20 13:03:04 We always use typeof when checking for undefined.
kzar 2016/05/21 04:43:09 Acknowledged.
179 { 207 // The new filter's selector is unconditionally applied to all domains
180 filterId = selectorByFilterId.push(filter.selector) - 1; 208 let filters = filtersBySelector[filter.selector];
181 filterIdByFilterText[filter.text] = filterId; 209 if (filters)
182 } 210 {
183 211 filters.push(filter);
184 let domainMatches = filter.domains || defaultDomains; 212 }
185
Sebastian Noack 2016/05/20 13:03:03 Nit: It seems the code reads better without the bl
kzar 2016/05/21 04:43:09 Done.
186 for (let domain in domainMatches)
187 {
188 let lookup;
189 if (domainMatches[domain])
190 lookup = activeFilterIdsByDomain;
191 else 213 else
192 lookup = inactiveFilterIdsByDomain; 214 {
193 215 filtersBySelector[filter.selector] = [filter];
194 let filterIds = lookup[domain]; 216 unconditionalSelectors = null;
195 if (filterIds == undefined) 217 }
196 filterIds = lookup[domain] = []; 218 }
197 219 else
198 filterIds.push(filterId); 220 {
221 // The new filter's selector only applies to some domains
222 this._addToFiltersByDomain(filter);
199 } 223 }
200 } 224 }
201 225
202 ElemHide.isDirty = true; 226 ElemHide.isDirty = true;
203 } 227 }
204 }, 228 },
205 229
206 /** 230 /**
207 * Removes an element hiding filter 231 * Removes an element hiding filter
208 * @param {ElemHideFilter} filter 232 * @param {ElemHideFilter} filter
(...skipping 14 matching lines...) Expand all
223 else 247 else
224 { 248 {
225 if (!(filter.text in keyByFilter)) 249 if (!(filter.text in keyByFilter))
226 return; 250 return;
227 251
228 let key = keyByFilter[filter.text]; 252 let key = keyByFilter[filter.text];
229 delete filterByKey[key]; 253 delete filterByKey[key];
230 delete keyByFilter[filter.text]; 254 delete keyByFilter[filter.text];
231 ElemHide.isDirty = true; 255 ElemHide.isDirty = true;
232 256
233 if (usingGetSelectorsByDomain) 257 if (usingGetSelectorsForDomain)
kzar 2016/05/20 10:55:17 I'm not too happy with this change it's kind of sl
234 { 258 {
235 let filterId = filterIdByFilterText[filter.text]; 259 let filters = filtersBySelector[filter.selector];
236 if (filterId) 260 if (filters)
237 { 261 {
238 delete filterIdByFilterText[filter.text]; 262 if (filters.length > 1)
239 delete selectorByFilterId[filterId]; 263 {
240 264 let index = filters.indexOf(filter);
241 let domains = Object.keys(filter.domains || defaultDomains); 265 filters.splice(index, 1);
Wladimir Palant 2016/05/20 12:33:27 Why use Object.keys() here all the sudden rather t
kzar 2016/05/21 04:43:09 Done.
242 let filterIdsByDomainLookups = [activeFilterIdsByDomain, 266 }
243 inactiveFilterIdsByDomain]; 267 else
244 for (let domain of domains) 268 {
245 { 269 delete filtersBySelector[filter.selector];
246 for (let filterIdsByDomain of filterIdsByDomainLookups) 270 unconditionalSelectors = null;
247 { 271 }
248 let lookup = filterIdsByDomain[domain]; 272 }
249 if (lookup) 273 else
250 { 274 {
251 let index = lookup.indexOf(filterId); 275 let domains = filter.domains || defaultDomains;
252 if (index != -1) 276 for (let domain in domains)
253 lookup.splice(index, 1); 277 {
254 } 278 let filters = filtersByDomain[domain];
255 } 279 if (filters)
280 delete filters[key];
256 } 281 }
257 } 282 }
258 } 283 }
259 } 284 }
260 }, 285 },
261 286
262 /** 287 /**
263 * Checks whether an exception rule is registered for a filter on a particular 288 * Checks whether an exception rule is registered for a filter on a particular
264 * domain. 289 * domain.
265 */ 290 */
266 getException: function(/**String*/ selector, /**String*/ docDomain) /**ElemHid eException*/ 291 getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideE xception*/
267 { 292 {
268 if (!(selector in exceptions)) 293 if (!(filter.selector in exceptions))
269 return null; 294 return null;
270 295
271 let list = exceptions[selector]; 296 let list = exceptions[filter.selector];
272 for (let i = list.length - 1; i >= 0; i--) 297 for (let i = list.length - 1; i >= 0; i--)
273 if (list[i].isActiveOnDomain(docDomain)) 298 if (list[i].isActiveOnDomain(docDomain))
274 return list[i]; 299 return list[i];
275 300
276 return null; 301 return null;
277 }, 302 },
278 303
279 /** 304 /**
280 * Will be set to true if apply() is running (reentrance protection). 305 * Will be set to true if apply() is running (reentrance protection).
281 * @type Boolean 306 * @type Boolean
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
371 }, 396 },
372 397
373 _generateCSSContent: function*() 398 _generateCSSContent: function*()
374 { 399 {
375 // Grouping selectors by domains 400 // Grouping selectors by domains
376 let domains = Object.create(null); 401 let domains = Object.create(null);
377 let hasFilters = false; 402 let hasFilters = false;
378 for (let key in filterByKey) 403 for (let key in filterByKey)
379 { 404 {
380 let filter = filterByKey[key]; 405 let filter = filterByKey[key];
406 let selector = filter.selector;
407 if (!selector)
408 continue;
409
381 let domain = filter.selectorDomain || ""; 410 let domain = filter.selectorDomain || "";
382 411
383 let list; 412 let list;
384 if (domain in domains) 413 if (domain in domains)
385 list = domains[domain]; 414 list = domains[domain];
386 else 415 else
387 { 416 {
388 list = Object.create(null); 417 list = Object.create(null);
389 domains[domain] = list; 418 domains[domain] = list;
390 } 419 }
391 list[filter.selector] = key; 420 list[selector] = key;
392 hasFilters = true; 421 hasFilters = true;
393 } 422 }
394 423
395 if (!hasFilters) 424 if (!hasFilters)
396 throw Cr.NS_ERROR_NOT_AVAILABLE; 425 throw Cr.NS_ERROR_NOT_AVAILABLE;
397 426
398 function escapeChar(match) 427 function escapeChar(match)
399 { 428 {
400 return "\\" + match.charCodeAt(0).toString(16) + " "; 429 return "\\" + match.charCodeAt(0).toString(16) + " ";
401 } 430 }
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
454 /** 483 /**
455 * Retrieves an element hiding filter by the corresponding protocol key 484 * Retrieves an element hiding filter by the corresponding protocol key
456 */ 485 */
457 getFilterByKey: function(/**String*/ key) /**Filter*/ 486 getFilterByKey: function(/**String*/ key) /**Filter*/
458 { 487 {
459 return (key in filterByKey ? filterByKey[key] : null); 488 return (key in filterByKey ? filterByKey[key] : null);
460 }, 489 },
461 490
462 /** 491 /**
463 * Returns a list of all selectors active on a particular domain, must not be 492 * Returns a list of all selectors active on a particular domain, must not be
464 * used in Firefox (when usingFiltersByDomain is false). 493 * used in Firefox (when usingGetSelectorsForDomain is false).
465 */ 494 */
466 getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) 495 getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly)
467 { 496 {
468 if (!usingGetSelectorsByDomain) 497 if (!usingGetSelectorsForDomain)
469 throw new Error("getSelectorsForDomain can not be used in Firefox!"); 498 throw new Error("getSelectorsForDomain can not be used in Firefox!");
470 499
471 let selectors = []; 500 if (!unconditionalSelectors)
472 501 unconditionalSelectors = Object.keys(filtersBySelector);
473 let seenFilterIds = Object.create(null); 502 let selectors = specificOnly ? [] : unconditionalSelectors.slice();
503
504 let seenFilters = Object.create(null);
474 let currentDomain = domain ? domain.toUpperCase() : ""; 505 let currentDomain = domain ? domain.toUpperCase() : "";
475 while (true) 506 while (true)
476 { 507 {
477 if (specificOnly && currentDomain == "") 508 if (specificOnly && currentDomain == "")
478 break; 509 break;
479 510
480 let inactiveFilterIds = inactiveFilterIdsByDomain[currentDomain]; 511 let filters = filtersByDomain[currentDomain];
481 if (inactiveFilterIds) 512 if (filters)
482 for (let filterId of inactiveFilterIds) 513 {
483 seenFilterIds[filterId] = true; 514 for (let filterKey in filters)
484 515 {
485 let activeFilterIds = activeFilterIdsByDomain[currentDomain]; 516 if (filterKey in seenFilters)
486 if (activeFilterIds)
487 {
488 for (let filterId of activeFilterIds)
489 {
490 if (filterId in seenFilterIds)
491 continue; 517 continue;
492 seenFilterIds[filterId] = true; 518 seenFilters[filterKey] = true;
493 519
494 let selector = selectorByFilterId[filterId]; 520 let filter = filters[filterKey];
495 if (!this.getException(selector, domain)) 521 if (filter && !this.getException(filter, domain))
496 selectors.push(selector); 522 selectors.push(filter.selector);
497 } 523 }
498 } 524 }
499 525
500 if (currentDomain == "") 526 if (currentDomain == "")
501 break; 527 break;
502 528
503 let nextDot = currentDomain.indexOf("."); 529 let nextDot = currentDomain.indexOf(".");
504 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); 530 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
505 } 531 }
506 532
507 return selectors; 533 return selectors;
508 } 534 }
509 }; 535 };
LEFTRIGHT
« no previous file | test/elemHide.js » ('j') | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld