Left: | ||
Right: |
LEFT | RIGHT |
---|---|
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 10 matching lines...) Expand all Loading... | |
21 | 21 |
22 Cu.import("resource://gre/modules/Services.jsm"); | 22 Cu.import("resource://gre/modules/Services.jsm"); |
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 let Policy = null; | |
31 | 32 |
32 /** | 33 /** |
33 * Lookup table, filters by their associated key | 34 * Lookup table, filters by their associated key |
34 * @type Object | 35 * @type Object |
35 */ | 36 */ |
36 let filterByKey = {__proto__: null}; | 37 let filterByKey = Object.create(null); |
37 | 38 |
38 /** | 39 /** |
39 * Lookup table, keys of the filters by filter text | 40 * Lookup table, keys of the filters by filter text |
40 * @type Object | 41 * @type Object |
41 */ | 42 */ |
42 let keyByFilter = {__proto__: null}; | 43 let keyByFilter = Object.create(null); |
43 | 44 |
44 /** | 45 /** |
45 * Lookup table, keys are known element hiding exceptions | 46 * Lookup table, keys are known element hiding exceptions |
46 * @type Object | 47 * @type Object |
47 */ | 48 */ |
48 let knownExceptions = {__proto__: null}; | 49 let knownExceptions = Object.create(null); |
49 | 50 |
50 /** | 51 /** |
51 * Lookup table, lists of element hiding exceptions by selector | 52 * Lookup table, lists of element hiding exceptions by selector |
52 * @type Object | 53 * @type Object |
53 */ | 54 */ |
54 let exceptions = {__proto__: null}; | 55 let exceptions = Object.create(null); |
55 | 56 |
56 /** | 57 /** |
57 * Currently applied stylesheet URL | 58 * Currently applied stylesheet URL |
58 * @type nsIURI | 59 * @type nsIURI |
59 */ | 60 */ |
60 let styleURL = null; | 61 let styleURL = null; |
61 | 62 |
62 /** | 63 /** |
64 * Global stylesheet that should be loaded into content windows. | |
65 * @type nsIStyleSheet | |
66 */ | |
67 let styleSheet = null; | |
68 | |
69 /** | |
70 * Use the new way of injecting styles per window that exists since Firefox 33. | |
71 * @type boolean | |
72 */ | |
73 let useNew = ('preloadSheet' in Utils.styleService); | |
74 | |
75 /** | |
63 * Element hiding component | 76 * Element hiding component |
64 * @class | 77 * @class |
65 */ | 78 */ |
66 let ElemHide = exports.ElemHide = | 79 let ElemHide = exports.ElemHide = |
67 { | 80 { |
81 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakRefer ence]), | |
82 | |
68 /** | 83 /** |
69 * Indicates whether filters have been added or removed since the last apply() call. | 84 * Indicates whether filters have been added or removed since the last apply() call. |
70 * @type Boolean | 85 * @type Boolean |
71 */ | 86 */ |
72 isDirty: false, | 87 isDirty: false, |
73 | 88 |
74 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakRefer ence]), | 89 /** |
90 * Inidicates whether the element hiding stylesheet is currently applied. | |
91 * @type Boolean | |
92 */ | |
93 applied: false, | |
75 | 94 |
76 /** | 95 /** |
77 * Called on module startup. | 96 * Called on module startup. |
78 */ | 97 */ |
79 init: function() | 98 init: function() |
80 { | 99 { |
81 TimeLine.enter("Entered ElemHide.init()"); | 100 TimeLine.enter("Entered ElemHide.init()"); |
101 | |
102 if (useNew) { | |
103 // Avoid dependency issue. | |
104 Policy = require("contentPolicy").Policy; | |
105 } | |
106 | |
82 Prefs.addListener(function(name) | 107 Prefs.addListener(function(name) |
83 { | 108 { |
84 if (name == "enabled") | 109 if (name == "enabled") |
85 ElemHide.apply(); | 110 ElemHide.apply(); |
86 }); | 111 }); |
Wladimir Palant
2014/05/20 19:36:12
This code is no longer necessary.
| |
87 onShutdown.add(function() | 112 |
88 { | 113 if (useNew) |
89 Services.obs.removeObserver(this, "content-document-global-created"); | 114 Services.obs.addObserver(this, "content-document-global-created", true); |
90 }.bind(this)); | 115 |
116 onShutdown.add(() => | |
117 { | |
118 ElemHide.unapply(); | |
119 if (useNew) | |
120 Services.obs.removeObserver(this, "content-document-global-created"); | |
121 }); | |
91 | 122 |
92 TimeLine.log("done adding prefs listener"); | 123 TimeLine.log("done adding prefs listener"); |
93 | 124 |
94 let styleFile = IO.resolveFilePath(Prefs.data_directory); | 125 let styleFile = IO.resolveFilePath(Prefs.data_directory); |
95 styleFile.append("elemhide.css"); | 126 styleFile.append("elemhide.css"); |
96 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL); | 127 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL); |
97 TimeLine.log("done determining stylesheet URL"); | 128 TimeLine.log("done determining stylesheet URL"); |
98 | 129 |
99 Services.obs.addObserver(this, "content-document-global-created", true); | |
100 | |
101 TimeLine.leave("ElemHide.init() done"); | 130 TimeLine.leave("ElemHide.init() done"); |
102 }, | |
103 | |
104 /** | |
105 * Removes all known filters | |
106 */ | |
107 clear: function() | |
108 { | |
109 filterByKey = {__proto__: null}; | |
110 keyByFilter = {__proto__: null}; | |
111 knownExceptions = {__proto__: null}; | |
112 exceptions = {__proto__: null}; | |
113 ElemHide.isDirty = false; | |
114 }, | 131 }, |
115 | 132 |
116 observe: function (subject, topic, data, additional) | 133 observe: function (subject, topic, data, additional) |
117 { | 134 { |
118 if (topic != "content-document-global-created") | 135 if (topic != "content-document-global-created") |
119 return; | 136 return; |
120 | 137 |
121 if (!Prefs.enabled) | 138 if (!Prefs.enabled) |
122 return; | 139 return; |
123 | 140 |
124 let uri = subject.document.documentURIObject; | 141 if (Policy.shouldNeverBlockWindow(subject)) |
125 if (!uri) | |
126 return; | 142 return; |
127 | 143 |
128 switch(uri.scheme) | 144 if (styleSheet) |
129 { | 145 { |
130 case "http": | 146 try |
131 case "https": | 147 { |
132 case "mailbox": | 148 let utils = subject.QueryInterface(Ci.nsIInterfaceRequestor) |
133 case "imap": | 149 .getInterface(Ci.nsIDOMWindowUtils); |
134 case "news": | 150 utils.addSheet(styleSheet, Ci.nsIStyleSheetService.USER_SHEET); |
135 case "snews": | 151 } |
136 break; | 152 catch (e) |
137 default: | 153 { |
138 // Avoid injecting in chrome documents | 154 Cu.reportError(e); |
139 return; | 155 } |
140 } | 156 } |
Wladimir Palant
2014/05/20 19:36:12
Please use Policy.isBlockableScheme() here (from c
| |
141 | 157 }, |
142 // XXX is there some better way to test if it's disabled on that domain that we should do before? | 158 |
143 if (!ElemHide.anySelectorForDomain(uri.host)) | 159 /** |
144 return; | 160 * Removes all known filters |
Wladimir Palant
2014/05/20 19:36:12
Wow, performance will go down the drain here. We c
| |
145 | 161 */ |
146 var windowUtils = subject.QueryInterface(Ci.nsIInterfaceRequestor).getInterf ace(Ci.nsIDOMWindowUtils); | 162 clear: function() |
147 windowUtils.loadSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET); | 163 { |
164 filterByKey = Object.create(null); | |
165 keyByFilter = Object.create(null); | |
166 knownExceptions = Object.create(null); | |
167 exceptions = Object.create(null); | |
168 ElemHide.isDirty = false; | |
169 ElemHide.unapply(); | |
148 }, | 170 }, |
149 | 171 |
150 /** | 172 /** |
151 * Add a new element hiding filter | 173 * Add a new element hiding filter |
152 * @param {ElemHideFilter} filter | 174 * @param {ElemHideFilter} filter |
153 */ | 175 */ |
154 add: function(filter) | 176 add: function(filter) |
155 { | 177 { |
156 if (filter instanceof ElemHideException) | 178 if (filter instanceof ElemHideException) |
157 { | 179 { |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
234 _applying: false, | 256 _applying: false, |
235 | 257 |
236 /** | 258 /** |
237 * Will be set to true if an apply() call arrives while apply() is already | 259 * Will be set to true if an apply() call arrives while apply() is already |
238 * running (delayed execution). | 260 * running (delayed execution). |
239 * @type Boolean | 261 * @type Boolean |
240 */ | 262 */ |
241 _needsApply: false, | 263 _needsApply: false, |
242 | 264 |
243 /** | 265 /** |
244 * Generates stylesheet URL. | 266 * Generates stylesheet URL and applies it globally |
245 */ | 267 */ |
246 // XXX Rename | |
247 apply: function() | 268 apply: function() |
Wladimir Palant
2014/05/20 19:36:12
saveStylesheet()?
| |
248 { | 269 { |
249 if (this._applying) | 270 if (this._applying) |
250 { | 271 { |
251 this._needsApply = true; | 272 this._needsApply = true; |
252 return; | 273 return; |
253 } | 274 } |
254 | 275 |
255 TimeLine.enter("Entered ElemHide.apply()"); | 276 TimeLine.enter("Entered ElemHide.apply()"); |
277 | |
278 if (!ElemHide.isDirty || !Prefs.enabled) | |
279 { | |
280 // Nothing changed, looks like we merely got enabled/disabled | |
281 if (Prefs.enabled && !ElemHide.applied) | |
282 { | |
283 try | |
284 { | |
285 if (!useNew) | |
286 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetSe rvice.USER_SHEET); | |
287 ElemHide.applied = true; | |
288 } | |
289 catch (e) | |
290 { | |
291 Cu.reportError(e); | |
292 } | |
293 TimeLine.log("Applying existing stylesheet finished"); | |
294 } | |
295 else if (!Prefs.enabled && ElemHide.applied) | |
296 { | |
297 ElemHide.unapply(); | |
298 TimeLine.log("ElemHide.unapply() finished"); | |
299 } | |
300 | |
301 TimeLine.leave("ElemHide.apply() done (no file changes)"); | |
302 return; | |
303 } | |
256 | 304 |
257 IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e) | 305 IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e) |
258 { | 306 { |
259 TimeLine.enter("ElemHide.apply() write callback"); | 307 TimeLine.enter("ElemHide.apply() write callback"); |
260 this._applying = false; | 308 this._applying = false; |
261 | 309 |
262 // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that | 310 // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that |
263 // there are no filters. If that exception is passed through XPCOM we will | 311 // there are no filters. If that exception is passed through XPCOM we will |
264 // see a proper exception here, otherwise a number. | 312 // see a proper exception here, otherwise a number. |
265 let noFilters = (e == Cr.NS_ERROR_NOT_AVAILABLE || (e && e.result == Cr.NS _ERROR_NOT_AVAILABLE)); | 313 let noFilters = (e == Cr.NS_ERROR_NOT_AVAILABLE || (e && e.result == Cr.NS _ERROR_NOT_AVAILABLE)); |
266 if (noFilters) | 314 if (noFilters) |
267 { | 315 { |
268 e = null; | 316 e = null; |
269 IO.removeFile(styleURL.file, function(e) {}); | 317 IO.removeFile(styleURL.file, function(e) {}); |
270 } | 318 } |
271 else if (e) | 319 else if (e) |
272 Cu.reportError(e); | 320 Cu.reportError(e); |
273 | 321 |
274 if (this._needsApply) | 322 if (this._needsApply) |
275 { | 323 { |
276 this._needsApply = false; | 324 this._needsApply = false; |
277 this.apply(); | 325 this.apply(); |
278 } | 326 } |
279 else if (!e) | 327 else if (!e) |
280 { | 328 { |
281 ElemHide.isDirty = false; | 329 ElemHide.isDirty = false; |
282 | 330 |
331 ElemHide.unapply(); | |
332 TimeLine.log("ElemHide.unapply() finished"); | |
333 | |
334 if (!noFilters) | |
335 { | |
336 try | |
337 { | |
338 if (!useNew) | |
339 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheet Service.USER_SHEET); | |
340 else | |
341 styleSheet = Utils.styleService.preloadSheet(styleURL, Ci.nsIStyle SheetService.USER_SHEET); | |
342 ElemHide.applied = true; | |
343 } | |
344 catch (e) | |
345 { | |
346 Cu.reportError(e); | |
347 } | |
348 TimeLine.log("Applying stylesheet finished"); | |
349 } | |
350 | |
283 FilterNotifier.triggerListeners("elemhideupdate"); | 351 FilterNotifier.triggerListeners("elemhideupdate"); |
Wladimir Palant
2014/05/20 19:36:12
Apparently, this is dead code. This notification h
| |
284 } | 352 } |
285 TimeLine.leave("ElemHide.apply() write callback done"); | 353 TimeLine.leave("ElemHide.apply() write callback done"); |
286 }.bind(this), "ElemHideWrite"); | 354 }.bind(this), "ElemHideWrite"); |
287 | 355 |
288 this._applying = true; | 356 this._applying = true; |
289 | 357 |
290 TimeLine.leave("ElemHide.apply() done", "ElemHideWrite"); | 358 TimeLine.leave("ElemHide.apply() done", "ElemHideWrite"); |
291 }, | 359 }, |
292 | 360 |
293 _generateCSSContent: function() | 361 _generateCSSContent: function() |
294 { | 362 { |
295 // Grouping selectors by domains | 363 // Grouping selectors by domains |
296 TimeLine.log("start grouping selectors"); | 364 TimeLine.log("start grouping selectors"); |
297 let domains = {__proto__: null}; | 365 let domains = Object.create(null); |
298 let hasFilters = false; | 366 let hasFilters = false; |
299 for (let key in filterByKey) | 367 for (let key in filterByKey) |
300 { | 368 { |
301 let filter = filterByKey[key]; | 369 let filter = filterByKey[key]; |
302 let domain = filter.selectorDomain || ""; | 370 let domain = filter.selectorDomain || ""; |
303 | 371 |
304 let list; | 372 let list; |
305 if (domain in domains) | 373 if (domain in domains) |
306 list = domains[domain]; | 374 list = domains[domain]; |
307 else | 375 else |
308 { | 376 { |
309 list = {__proto__: null}; | 377 list = Object.create(null); |
310 domains[domain] = list; | 378 domains[domain] = list; |
311 } | 379 } |
312 list[filter.selector] = key; | 380 list[filter.selector] = key; |
313 hasFilters = true; | 381 hasFilters = true; |
314 } | 382 } |
315 TimeLine.log("done grouping selectors"); | 383 TimeLine.log("done grouping selectors"); |
316 | 384 |
317 if (!hasFilters) | 385 if (!hasFilters) |
318 throw Cr.NS_ERROR_NOT_AVAILABLE; | 386 throw Cr.NS_ERROR_NOT_AVAILABLE; |
319 | 387 |
(...skipping 23 matching lines...) Expand all Loading... | |
343 yield selector.replace(/[^\x01-\x7F]/g, escapeChar) + "{" + cssTemplate. replace("%ID%", list[selector]) + "}"; | 411 yield selector.replace(/[^\x01-\x7F]/g, escapeChar) + "{" + cssTemplate. replace("%ID%", list[selector]) + "}"; |
344 yield '}'; | 412 yield '}'; |
345 } | 413 } |
346 }, | 414 }, |
347 | 415 |
348 /** | 416 /** |
349 * Unapplies current stylesheet URL | 417 * Unapplies current stylesheet URL |
350 */ | 418 */ |
351 unapply: function() | 419 unapply: function() |
352 { | 420 { |
353 }, | 421 if (ElemHide.applied) |
Wladimir Palant
2014/05/20 19:36:12
This function should be removed.
| |
354 | 422 { |
355 /** | 423 try |
356 * Retrieves the currently applied stylesheet URL | 424 { |
357 * @type String | 425 if (!useNew) |
358 */ | 426 Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.U SER_SHEET); |
359 get styleURL() styleURL.spec, | 427 } |
Wladimir Palant
2014/05/20 19:36:12
This getter was also introduced in https://hg.adbl
| |
428 catch (e) | |
429 { | |
430 Cu.reportError(e); | |
431 } | |
432 ElemHide.applied = false; | |
433 } | |
434 }, | |
360 | 435 |
361 /** | 436 /** |
362 * Retrieves an element hiding filter by the corresponding protocol key | 437 * Retrieves an element hiding filter by the corresponding protocol key |
363 */ | 438 */ |
364 getFilterByKey: function(/**String*/ key) /**Filter*/ | 439 getFilterByKey: function(/**String*/ key) /**Filter*/ |
365 { | 440 { |
366 return (key in filterByKey ? filterByKey[key] : null); | 441 return (key in filterByKey ? filterByKey[key] : null); |
367 }, | 442 }, |
368 | 443 |
369 /** | 444 /** |
(...skipping 12 matching lines...) Expand all Loading... | |
382 // workaround WebKit bug 132872, also see #419 | 457 // workaround WebKit bug 132872, also see #419 |
383 let domains = filter.domains; | 458 let domains = filter.domains; |
384 | 459 |
385 if (specificOnly && (!domains || domains[""])) | 460 if (specificOnly && (!domains || domains[""])) |
386 continue; | 461 continue; |
387 | 462 |
388 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) | 463 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) |
389 result.push(filter.selector); | 464 result.push(filter.selector); |
390 } | 465 } |
391 return result; | 466 return result; |
392 }, | |
393 | |
394 anySelectorForDomain: function(/**String*/ domain) | |
395 { | |
396 for (let key in filterByKey) | |
397 { | |
398 let filter = filterByKey[key]; | |
399 | |
400 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) | |
401 return true; | |
402 } | |
403 | |
404 return false; | |
405 } | 467 } |
406 }; | 468 }; |
LEFT | RIGHT |