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

Side by Side Diff: lib/elemHide.js

Issue 29345663: Issue 4140 - Remove Firefox-specific element hiding functionality (Closed)
Patch Set: Created June 8, 2016, 8:23 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 | « no previous file | lib/filterListener.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-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 /** 18 /**
19 * @fileOverview Element hiding implementation. 19 * @fileOverview Element hiding implementation.
20 */ 20 */
21 21
22 Cu.import("resource://gre/modules/Services.jsm"); 22 let {Utils} = require("utils");
23 23 let {ElemHideException} = require("filterClasses");
24 var {Utils} = require("utils"); 24 let {FilterNotifier} = require("filterNotifier");
25 var {IO} = require("io");
26 var {Prefs} = require("prefs");
27 var {ElemHideException} = require("filterClasses");
28 var {FilterNotifier} = require("filterNotifier");
29 25
30 /** 26 /**
31 * Lookup table, filters by their associated key 27 * Lookup table, filters by their associated key
32 * @type Object 28 * @type Object
33 */ 29 */
34 var filterByKey = []; 30 var filterByKey = [];
35 31
36 /** 32 /**
37 * Lookup table, keys of the filters by filter text 33 * Lookup table, keys of the filters by filter text
38 * @type Object 34 * @type Object
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 */ 75 */
80 var knownExceptions = Object.create(null); 76 var knownExceptions = Object.create(null);
81 77
82 /** 78 /**
83 * Lookup table, lists of element hiding exceptions by selector 79 * Lookup table, lists of element hiding exceptions by selector
84 * @type Object 80 * @type Object
85 */ 81 */
86 var exceptions = Object.create(null); 82 var exceptions = Object.create(null);
87 83
88 /** 84 /**
89 * Currently applied stylesheet URL 85 * Container for element hiding filters
90 * @type nsIURI
91 */
92 var styleURL = null;
93
94 /**
95 * Element hiding component
96 * @class 86 * @class
97 */ 87 */
98 var ElemHide = exports.ElemHide = 88 var ElemHide = exports.ElemHide =
99 { 89 {
100 /** 90 /**
101 * Indicates whether filters have been added or removed since the last apply() call.
102 * @type Boolean
103 */
104 isDirty: false,
105
106 /**
107 * Indicates whether the element hiding stylesheet is currently applied.
108 * @type Boolean
109 */
110 applied: false,
111
112 /**
113 * Called on module startup.
114 */
115 init: function()
116 {
117 Prefs.addListener(function(name)
118 {
119 if (name == "enabled")
120 ElemHide.apply();
121 });
122 onShutdown.add(() => ElemHide.unapply());
123
124 let styleFile = IO.resolveFilePath(Prefs.data_directory);
125 styleFile.append("elemhide.css");
126 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL);
127 },
128
129 /**
130 * Removes all known filters 91 * Removes all known filters
131 */ 92 */
132 clear: function() 93 clear: function()
133 { 94 {
134 filterByKey = []; 95 filterByKey = [];
135 keyByFilter = Object.create(null); 96 keyByFilter = Object.create(null);
136 filtersByDomain = Object.create(null); 97 filtersByDomain = Object.create(null);
137 filtersBySelector = Object.create(null); 98 filtersBySelector = Object.create(null);
138 unconditionalSelectors = null; 99 unconditionalSelectors = null;
139 knownExceptions = Object.create(null); 100 knownExceptions = Object.create(null);
140 exceptions = Object.create(null); 101 exceptions = Object.create(null);
141 ElemHide.isDirty = false; 102 FilterNotifier.emit("elemhideupdate");
142 ElemHide.unapply();
143 }, 103 },
144 104
145 _addToFiltersByDomain: function(filter) 105 _addToFiltersByDomain: function(filter)
146 { 106 {
147 let key = keyByFilter[filter.text]; 107 let key = keyByFilter[filter.text];
148 let domains = filter.domains || defaultDomains; 108 let domains = filter.domains || defaultDomains;
149 for (let domain in domains) 109 for (let domain in domains)
150 { 110 {
151 let filters = filtersByDomain[domain]; 111 let filters = filtersByDomain[domain];
152 if (!filters) 112 if (!filters)
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
215 filtersBySelector[filter.selector] = [filter]; 175 filtersBySelector[filter.selector] = [filter];
216 unconditionalSelectors = null; 176 unconditionalSelectors = null;
217 } 177 }
218 } 178 }
219 else 179 else
220 { 180 {
221 // The new filter's selector only applies to some domains 181 // The new filter's selector only applies to some domains
222 this._addToFiltersByDomain(filter); 182 this._addToFiltersByDomain(filter);
223 } 183 }
224 } 184 }
185 }
225 186
226 ElemHide.isDirty = true; 187 FilterNotifier.emit("elemhideupdate");
227 }
228 }, 188 },
229 189
230 /** 190 /**
231 * Removes an element hiding filter 191 * Removes an element hiding filter
232 * @param {ElemHideFilter} filter 192 * @param {ElemHideFilter} filter
233 */ 193 */
234 remove: function(filter) 194 remove: function(filter)
235 { 195 {
236 if (filter instanceof ElemHideException) 196 if (filter instanceof ElemHideException)
237 { 197 {
238 if (!(filter.text in knownExceptions)) 198 if (!(filter.text in knownExceptions))
239 return; 199 return;
240 200
241 let list = exceptions[filter.selector]; 201 let list = exceptions[filter.selector];
242 let index = list.indexOf(filter); 202 let index = list.indexOf(filter);
243 if (index >= 0) 203 if (index >= 0)
244 list.splice(index, 1); 204 list.splice(index, 1);
245 delete knownExceptions[filter.text]; 205 delete knownExceptions[filter.text];
246 } 206 }
247 else 207 else
248 { 208 {
249 if (!(filter.text in keyByFilter)) 209 if (!(filter.text in keyByFilter))
250 return; 210 return;
251 211
252 let key = keyByFilter[filter.text]; 212 let key = keyByFilter[filter.text];
253 delete filterByKey[key]; 213 delete filterByKey[key];
254 delete keyByFilter[filter.text]; 214 delete keyByFilter[filter.text];
255 ElemHide.isDirty = true;
256 215
257 if (usingGetSelectorsForDomain) 216 if (usingGetSelectorsForDomain)
258 { 217 {
259 let filters = filtersBySelector[filter.selector]; 218 let filters = filtersBySelector[filter.selector];
260 if (filters) 219 if (filters)
261 { 220 {
262 if (filters.length > 1) 221 if (filters.length > 1)
263 { 222 {
264 let index = filters.indexOf(filter); 223 let index = filters.indexOf(filter);
265 filters.splice(index, 1); 224 filters.splice(index, 1);
266 } 225 }
267 else 226 else
268 { 227 {
269 delete filtersBySelector[filter.selector]; 228 delete filtersBySelector[filter.selector];
270 unconditionalSelectors = null; 229 unconditionalSelectors = null;
271 } 230 }
272 } 231 }
273 else 232 else
274 { 233 {
275 let domains = filter.domains || defaultDomains; 234 let domains = filter.domains || defaultDomains;
276 for (let domain in domains) 235 for (let domain in domains)
277 { 236 {
278 let filters = filtersByDomain[domain]; 237 let filters = filtersByDomain[domain];
279 if (filters) 238 if (filters)
280 delete filters[key]; 239 delete filters[key];
281 } 240 }
282 } 241 }
283 } 242 }
284 } 243 }
244
245 FilterNotifier.emit("elemhideupdate");
285 }, 246 },
286 247
287 /** 248 /**
288 * Checks whether an exception rule is registered for a filter on a particular 249 * Checks whether an exception rule is registered for a filter on a particular
289 * domain. 250 * domain.
290 */ 251 */
291 getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideE xception*/ 252 getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideE xception*/
292 { 253 {
293 if (!(filter.selector in exceptions)) 254 if (!(filter.selector in exceptions))
294 return null; 255 return null;
295 256
296 let list = exceptions[filter.selector]; 257 let list = exceptions[filter.selector];
297 for (let i = list.length - 1; i >= 0; i--) 258 for (let i = list.length - 1; i >= 0; i--)
298 if (list[i].isActiveOnDomain(docDomain)) 259 if (list[i].isActiveOnDomain(docDomain))
299 return list[i]; 260 return list[i];
300 261
301 return null; 262 return null;
302 }, 263 },
303 264
304 /** 265 /**
305 * Will be set to true if apply() is running (reentrance protection).
306 * @type Boolean
307 */
308 _applying: false,
309
310 /**
311 * Will be set to true if an apply() call arrives while apply() is already
312 * running (delayed execution).
313 * @type Boolean
314 */
315 _needsApply: false,
316
317 /**
318 * Generates stylesheet URL and applies it globally
319 */
320 apply: function()
321 {
322 if (this._applying)
323 {
324 this._needsApply = true;
325 return;
326 }
327
328 if (!ElemHide.isDirty || !Prefs.enabled)
329 {
330 // Nothing changed, looks like we merely got enabled/disabled
331 if (Prefs.enabled && !ElemHide.applied)
332 {
333 try
334 {
335 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetServ ice.USER_SHEET);
336 ElemHide.applied = true;
337 }
338 catch (e)
339 {
340 Cu.reportError(e);
341 }
342 }
343 else if (!Prefs.enabled && ElemHide.applied)
344 {
345 ElemHide.unapply();
346 }
347
348 return;
349 }
350
351 IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e)
352 {
353 this._applying = false;
354
355 // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that
356 // there are no filters. If that exception is passed through XPCOM we will
357 // see a proper exception here, otherwise a number.
358 let noFilters = (e == Cr.NS_ERROR_NOT_AVAILABLE || (e && e.result == Cr.NS _ERROR_NOT_AVAILABLE));
359 if (noFilters)
360 {
361 e = null;
362 IO.removeFile(styleURL.file, function(e) {});
363 }
364 else if (e)
365 Cu.reportError(e);
366
367 if (this._needsApply)
368 {
369 this._needsApply = false;
370 this.apply();
371 }
372 else if (!e)
373 {
374 ElemHide.isDirty = false;
375
376 ElemHide.unapply();
377
378 if (!noFilters)
379 {
380 try
381 {
382 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetSe rvice.USER_SHEET);
383 ElemHide.applied = true;
384 }
385 catch (e)
386 {
387 Cu.reportError(e);
388 }
389 }
390
391 FilterNotifier.triggerListeners("elemhideupdate");
392 }
393 }.bind(this));
394
395 this._applying = true;
396 },
397
398 _generateCSSContent: function*()
399 {
400 // Grouping selectors by domains
401 let domains = Object.create(null);
402 let hasFilters = false;
403 for (let key in filterByKey)
404 {
405 let filter = filterByKey[key];
406 let selector = filter.selector;
407 if (!selector)
408 continue;
409
410 let domain = filter.selectorDomain || "";
411
412 let list;
413 if (domain in domains)
414 list = domains[domain];
415 else
416 {
417 list = Object.create(null);
418 domains[domain] = list;
419 }
420 list[selector] = key;
421 hasFilters = true;
422 }
423
424 if (!hasFilters)
425 throw Cr.NS_ERROR_NOT_AVAILABLE;
426
427 function escapeChar(match)
428 {
429 return "\\" + match.charCodeAt(0).toString(16) + " ";
430 }
431
432 // Return CSS data
433 let cssTemplate = "-moz-binding: url(about:abp-elemhidehit?%ID%#dummy) !impo rtant;";
434 for (let domain in domains)
435 {
436 let rules = [];
437 let list = domains[domain];
438
439 if (domain)
440 yield ('@-moz-document domain("' + domain.split(",").join('"),domain("') + '"){').replace(/[^\x01-\x7F]/g, escapeChar);
441 else
442 {
443 // Only allow unqualified rules on a few protocols to prevent them from blocking chrome
444 yield '@-moz-document url-prefix("http://"),url-prefix("https://"),'
445 + 'url-prefix("mailbox://"),url-prefix("imap://"),'
446 + 'url-prefix("news://"),url-prefix("snews://"){';
447 }
448
449 for (let selector in list)
450 yield selector.replace(/[^\x01-\x7F]/g, escapeChar) + "{" + cssTemplate. replace("%ID%", list[selector]) + "}";
451 yield '}';
452 }
453 },
454
455 /**
456 * Unapplies current stylesheet URL
457 */
458 unapply: function()
459 {
460 if (ElemHide.applied)
461 {
462 try
463 {
464 Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.USE R_SHEET);
465 }
466 catch (e)
467 {
468 Cu.reportError(e);
469 }
470 ElemHide.applied = false;
471 }
472 },
473
474 /**
475 * Retrieves the currently applied stylesheet URL
476 * @type String
477 */
478 get styleURL()
479 {
480 return ElemHide.applied ? styleURL.spec : null;
481 },
482
483 /**
484 * Retrieves an element hiding filter by the corresponding protocol key 266 * Retrieves an element hiding filter by the corresponding protocol key
485 */ 267 */
486 getFilterByKey: function(/**String*/ key) /**Filter*/ 268 getFilterByKey: function(/**String*/ key) /**Filter*/
487 { 269 {
488 return (key in filterByKey ? filterByKey[key] : null); 270 return (key in filterByKey ? filterByKey[key] : null);
489 }, 271 },
490 272
491 /** 273 /**
274 * Returns a list of all selectors as a nested map. On first level, the keys
275 * are all values of `ElemHideBase.selectorDomain` (domains on which these
276 * selectors should apply, ignoring exceptions). The values are maps again,
277 * with the keys being selectors and values the corresponding filter keys.
278 * @returns {Map.<String,Map<String,String>>}
279 */
280 getSelectors: function()
281 {
282 let domains = new Map();
283 for (let key in filterByKey)
284 {
285 let filter = filterByKey[key];
286 let selector = filter.selector;
287 if (!selector)
288 continue;
289
290 let domain = filter.selectorDomain || "";
291
292 if (!domains.has(domain))
293 domains.set(domain, new Map());
294 domains.get(domain).set(selector, key);
295 }
296
297 return domains;
298 },
299
300 /**
492 * Returns a list of all selectors active on a particular domain, must not be 301 * Returns a list of all selectors active on a particular domain, must not be
493 * used in Firefox (when usingGetSelectorsForDomain is false). 302 * used in Firefox (when usingGetSelectorsForDomain is false).
494 */ 303 */
495 getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) 304 getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly)
496 { 305 {
497 if (!usingGetSelectorsForDomain) 306 if (!usingGetSelectorsForDomain)
498 throw new Error("getSelectorsForDomain can not be used in Firefox!"); 307 throw new Error("getSelectorsForDomain can not be used in Firefox!");
499 308
500 if (!unconditionalSelectors) 309 if (!unconditionalSelectors)
501 unconditionalSelectors = Object.keys(filtersBySelector); 310 unconditionalSelectors = Object.keys(filtersBySelector);
(...skipping 24 matching lines...) Expand all
526 if (currentDomain == "") 335 if (currentDomain == "")
527 break; 336 break;
528 337
529 let nextDot = currentDomain.indexOf("."); 338 let nextDot = currentDomain.indexOf(".");
530 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); 339 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
531 } 340 }
532 341
533 return selectors; 342 return selectors;
534 } 343 }
535 }; 344 };
OLDNEW
« no previous file with comments | « no previous file | lib/filterListener.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld