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 |
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 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 {Policy} = require("contentPolicy"); | |
28 let {ElemHideException} = require("filterClasses"); | 27 let {ElemHideException} = require("filterClasses"); |
29 let {FilterNotifier} = require("filterNotifier"); | 28 let {FilterNotifier} = require("filterNotifier"); |
30 let {AboutHandler} = require("elemHideHitRegistration"); | 29 let {AboutHandler} = require("elemHideHitRegistration"); |
31 let {TimeLine} = require("timeline"); | 30 let {TimeLine} = require("timeline"); |
| 31 let Policy = null; |
32 | 32 |
33 /** | 33 /** |
34 * Lookup table, filters by their associated key | 34 * Lookup table, filters by their associated key |
35 * @type Object | 35 * @type Object |
36 */ | 36 */ |
37 let filterByKey = {__proto__: null}; | 37 let filterByKey = Object.create(null); |
38 | 38 |
39 /** | 39 /** |
40 * Lookup table, keys of the filters by filter text | 40 * Lookup table, keys of the filters by filter text |
41 * @type Object | 41 * @type Object |
42 */ | 42 */ |
43 let keyByFilter = {__proto__: null}; | 43 let keyByFilter = Object.create(null); |
44 | 44 |
45 /** | 45 /** |
46 * Lookup table, keys are known element hiding exceptions | 46 * Lookup table, keys are known element hiding exceptions |
47 * @type Object | 47 * @type Object |
48 */ | 48 */ |
49 let knownExceptions = {__proto__: null}; | 49 let knownExceptions = Object.create(null); |
50 | 50 |
51 /** | 51 /** |
52 * Lookup table, lists of element hiding exceptions by selector | 52 * Lookup table, lists of element hiding exceptions by selector |
53 * @type Object | 53 * @type Object |
54 */ | 54 */ |
55 let exceptions = {__proto__: null}; | 55 let exceptions = Object.create(null); |
56 | 56 |
57 /** | 57 /** |
58 * Currently applied stylesheet URL | 58 * Currently applied stylesheet URL |
59 * @type nsIURI | 59 * @type nsIURI |
60 */ | 60 */ |
61 let styleURL = null; | 61 let styleURL = null; |
62 | 62 |
63 /** | 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 /** |
64 * Element hiding component | 76 * Element hiding component |
65 * @class | 77 * @class |
66 */ | 78 */ |
67 let ElemHide = exports.ElemHide = | 79 let ElemHide = exports.ElemHide = |
68 { | 80 { |
69 /** | 81 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakRefer
ence]), |
70 * Indicates whether filters have been added or removed since the last saveSty
lesheet() call. | 82 |
| 83 /** |
| 84 * Indicates whether filters have been added or removed since the last apply()
call. |
71 * @type Boolean | 85 * @type Boolean |
72 */ | 86 */ |
73 isDirty: false, | 87 isDirty: false, |
74 | 88 |
75 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, |
76 | 94 |
77 /** | 95 /** |
78 * Called on module startup. | 96 * Called on module startup. |
79 */ | 97 */ |
80 init: function() | 98 init: function() |
81 { | 99 { |
82 TimeLine.enter("Entered ElemHide.init()"); | 100 TimeLine.enter("Entered ElemHide.init()"); |
83 | 101 |
84 onShutdown.add(function() | 102 if (useNew) { |
85 { | 103 // Avoid dependency issue. |
86 Services.obs.removeObserver(this, "content-document-global-created"); | 104 Policy = require("contentPolicy").Policy; |
87 }.bind(this)); | 105 } |
| 106 |
| 107 Prefs.addListener(function(name) |
| 108 { |
| 109 if (name == "enabled") |
| 110 ElemHide.apply(); |
| 111 }); |
| 112 |
| 113 if (useNew) |
| 114 Services.obs.addObserver(this, "content-document-global-created", true); |
| 115 |
| 116 onShutdown.add(() => |
| 117 { |
| 118 ElemHide.unapply(); |
| 119 if (useNew) |
| 120 Services.obs.removeObserver(this, "content-document-global-created"); |
| 121 }); |
88 | 122 |
89 TimeLine.log("done adding prefs listener"); | 123 TimeLine.log("done adding prefs listener"); |
90 | 124 |
91 let styleFile = IO.resolveFilePath(Prefs.data_directory); | 125 let styleFile = IO.resolveFilePath(Prefs.data_directory); |
92 styleFile.append("elemhide.css"); | 126 styleFile.append("elemhide.css"); |
93 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL); | 127 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL); |
94 TimeLine.log("done determining stylesheet URL"); | 128 TimeLine.log("done determining stylesheet URL"); |
95 | 129 |
96 Services.obs.addObserver(this, "content-document-global-created", true); | |
97 | |
98 TimeLine.leave("ElemHide.init() done"); | 130 TimeLine.leave("ElemHide.init() done"); |
99 }, | |
100 | |
101 /** | |
102 * Removes all known filters | |
103 */ | |
104 clear: function() | |
105 { | |
106 filterByKey = {__proto__: null}; | |
107 keyByFilter = {__proto__: null}; | |
108 knownExceptions = {__proto__: null}; | |
109 exceptions = {__proto__: null}; | |
110 ElemHide.isDirty = false; | |
111 }, | 131 }, |
112 | 132 |
113 observe: function (subject, topic, data, additional) | 133 observe: function (subject, topic, data, additional) |
114 { | 134 { |
115 if (topic != "content-document-global-created") | 135 if (topic != "content-document-global-created") |
116 return; | 136 return; |
117 | 137 |
118 if (!Prefs.enabled) | 138 if (!Prefs.enabled) |
119 return; | 139 return; |
120 | 140 |
121 let process = Policy.processWindow(subject, Policy.type.ELEMHIDE); | 141 if (Policy.shouldNeverBlockWindow(subject)) |
122 dump(subject.document.documentURIObject.spec + " is okay " + process + "\n"
); | |
123 if (process) | |
124 return; | 142 return; |
125 | 143 |
126 var windowUtils = subject.QueryInterface(Ci.nsIInterfaceRequestor).getInterf
ace(Ci.nsIDOMWindowUtils); | 144 if (styleSheet) |
127 windowUtils.loadSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET); | 145 { |
| 146 try |
| 147 { |
| 148 let utils = subject.QueryInterface(Ci.nsIInterfaceRequestor) |
| 149 .getInterface(Ci.nsIDOMWindowUtils); |
| 150 utils.addSheet(styleSheet, Ci.nsIStyleSheetService.USER_SHEET); |
| 151 } |
| 152 catch (e) |
| 153 { |
| 154 Cu.reportError(e); |
| 155 } |
| 156 } |
| 157 }, |
| 158 |
| 159 /** |
| 160 * Removes all known filters |
| 161 */ |
| 162 clear: function() |
| 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(); |
128 }, | 170 }, |
129 | 171 |
130 /** | 172 /** |
131 * Add a new element hiding filter | 173 * Add a new element hiding filter |
132 * @param {ElemHideFilter} filter | 174 * @param {ElemHideFilter} filter |
133 */ | 175 */ |
134 add: function(filter) | 176 add: function(filter) |
135 { | 177 { |
136 if (filter instanceof ElemHideException) | 178 if (filter instanceof ElemHideException) |
137 { | 179 { |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
200 return null; | 242 return null; |
201 | 243 |
202 let list = exceptions[filter.selector]; | 244 let list = exceptions[filter.selector]; |
203 for (let i = list.length - 1; i >= 0; i--) | 245 for (let i = list.length - 1; i >= 0; i--) |
204 if (list[i].isActiveOnDomain(docDomain)) | 246 if (list[i].isActiveOnDomain(docDomain)) |
205 return list[i]; | 247 return list[i]; |
206 | 248 |
207 return null; | 249 return null; |
208 }, | 250 }, |
209 | 251 |
210 shouldAllowLoad: function(wnd, filter) | 252 /** |
211 { | 253 * Will be set to true if apply() is running (reentrance protection). |
212 let uri = wnd.document.documentURIObject; | |
213 if (!uri) | |
214 return true; | |
215 | |
216 let domain = uri.host; | |
217 if (!filter.isActiveOnDomain(domain)) | |
218 return true; | |
219 | |
220 let exception = ElemHide.getException(filter, domain); | |
221 if (exception) | |
222 { | |
223 // FilterStorage.increaseHitCount(exception, wnd); | |
224 // RequestNotifier.addNodeData(node, topWnd, contentType, domain, thirdPar
ty, locationText, exception); | |
225 return true; | |
226 } | |
227 | |
228 // RequestNotifier.addNodeData(node, topWnd, contentType, domain, thirdParty
, locationText, match); | |
229 // if (match) | |
230 // FilterStorage.increaseHitCount(match, wnd); | |
231 return false; | |
232 }, | |
233 | |
234 /** | |
235 * Will be set to true if saveStylesheet() is running (reentrance protection). | |
236 * @type Boolean | 254 * @type Boolean |
237 */ | 255 */ |
238 _applying: false, | 256 _applying: false, |
239 | 257 |
240 /** | 258 /** |
241 * Will be set to true if an saveStylesheet() call arrives while saveStyleshee
t() | 259 * Will be set to true if an apply() call arrives while apply() is already |
242 * is already running (delayed execution). | 260 * running (delayed execution). |
243 * @type Boolean | 261 * @type Boolean |
244 */ | 262 */ |
245 _needsApply: false, | 263 _needsApply: false, |
246 | 264 |
247 /** | 265 /** |
248 * Generates stylesheet URL. | 266 * Generates stylesheet URL and applies it globally |
249 */ | 267 */ |
250 saveStylesheet: function() | 268 apply: function() |
251 { | 269 { |
252 if (this._applying) | 270 if (this._applying) |
253 { | 271 { |
254 this._needsApply = true; | 272 this._needsApply = true; |
255 return; | 273 return; |
256 } | 274 } |
257 | 275 |
258 TimeLine.enter("Entered ElemHide.saveStylesheet()"); | 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 } |
259 | 304 |
260 IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e) | 305 IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e) |
261 { | 306 { |
262 TimeLine.enter("ElemHide.saveStylesheet() write callback"); | 307 TimeLine.enter("ElemHide.apply() write callback"); |
263 this._applying = false; | 308 this._applying = false; |
264 | 309 |
265 // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that | 310 // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that |
266 // 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 |
267 // see a proper exception here, otherwise a number. | 312 // see a proper exception here, otherwise a number. |
268 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)); |
269 if (noFilters) | 314 if (noFilters) |
270 { | 315 { |
271 e = null; | 316 e = null; |
272 IO.removeFile(styleURL.file, function(e) {}); | 317 IO.removeFile(styleURL.file, function(e) {}); |
273 } | 318 } |
274 else if (e) | 319 else if (e) |
275 { | |
276 Cu.reportError(e); | 320 Cu.reportError(e); |
277 } | |
278 | 321 |
279 if (this._needsApply) | 322 if (this._needsApply) |
280 { | 323 { |
281 this._needsApply = false; | 324 this._needsApply = false; |
282 this.saveStylesheet(); | 325 this.apply(); |
283 } | 326 } |
284 else if (!e) | 327 else if (!e) |
285 { | 328 { |
286 ElemHide.isDirty = false; | 329 ElemHide.isDirty = false; |
287 | 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 |
288 FilterNotifier.triggerListeners("elemhideupdate"); | 351 FilterNotifier.triggerListeners("elemhideupdate"); |
289 } | 352 } |
290 TimeLine.leave("ElemHide.saveStylesheet() write callback done"); | 353 TimeLine.leave("ElemHide.apply() write callback done"); |
291 }.bind(this), "ElemHideWrite"); | 354 }.bind(this), "ElemHideWrite"); |
292 | 355 |
293 this._applying = true; | 356 this._applying = true; |
294 | 357 |
295 TimeLine.leave("ElemHide.saveStylesheet() done", "ElemHideWrite"); | 358 TimeLine.leave("ElemHide.apply() done", "ElemHideWrite"); |
296 }, | 359 }, |
297 | 360 |
298 _generateCSSContent: function() | 361 _generateCSSContent: function() |
299 { | 362 { |
300 // Grouping selectors by domains | 363 // Grouping selectors by domains |
301 TimeLine.log("start grouping selectors"); | 364 TimeLine.log("start grouping selectors"); |
302 let domains = {__proto__: null}; | 365 let domains = Object.create(null); |
303 let hasFilters = false; | 366 let hasFilters = false; |
304 for (let key in filterByKey) | 367 for (let key in filterByKey) |
305 { | 368 { |
306 let filter = filterByKey[key]; | 369 let filter = filterByKey[key]; |
307 let domain = filter.selectorDomain || ""; | 370 let domain = filter.selectorDomain || ""; |
308 | 371 |
309 let list; | 372 let list; |
310 if (domain in domains) | 373 if (domain in domains) |
311 list = domains[domain]; | 374 list = domains[domain]; |
312 else | 375 else |
313 { | 376 { |
314 list = {__proto__: null}; | 377 list = Object.create(null); |
315 domains[domain] = list; | 378 domains[domain] = list; |
316 } | 379 } |
317 list[filter.selector] = key; | 380 list[filter.selector] = key; |
318 hasFilters = true; | 381 hasFilters = true; |
319 } | 382 } |
320 TimeLine.log("done grouping selectors"); | 383 TimeLine.log("done grouping selectors"); |
321 | 384 |
322 if (!hasFilters) | 385 if (!hasFilters) |
323 throw Cr.NS_ERROR_NOT_AVAILABLE; | 386 throw Cr.NS_ERROR_NOT_AVAILABLE; |
324 | 387 |
(...skipping 19 matching lines...) Expand all Loading... |
344 + 'url-prefix("news://"),url-prefix("snews://"){'; | 407 + 'url-prefix("news://"),url-prefix("snews://"){'; |
345 } | 408 } |
346 | 409 |
347 for (let selector in list) | 410 for (let selector in list) |
348 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]) + "}"; |
349 yield '}'; | 412 yield '}'; |
350 } | 413 } |
351 }, | 414 }, |
352 | 415 |
353 /** | 416 /** |
| 417 * Unapplies current stylesheet URL |
| 418 */ |
| 419 unapply: function() |
| 420 { |
| 421 if (ElemHide.applied) |
| 422 { |
| 423 try |
| 424 { |
| 425 if (!useNew) |
| 426 Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.U
SER_SHEET); |
| 427 } |
| 428 catch (e) |
| 429 { |
| 430 Cu.reportError(e); |
| 431 } |
| 432 ElemHide.applied = false; |
| 433 } |
| 434 }, |
| 435 |
| 436 /** |
354 * Retrieves an element hiding filter by the corresponding protocol key | 437 * Retrieves an element hiding filter by the corresponding protocol key |
355 */ | 438 */ |
356 getFilterByKey: function(/**String*/ key) /**Filter*/ | 439 getFilterByKey: function(/**String*/ key) /**Filter*/ |
357 { | 440 { |
358 return (key in filterByKey ? filterByKey[key] : null); | 441 return (key in filterByKey ? filterByKey[key] : null); |
359 }, | 442 }, |
360 | 443 |
361 /** | 444 /** |
362 * Returns a list of all selectors active on a particular domain (currently | 445 * Returns a list of all selectors active on a particular domain (currently |
363 * used only in Chrome, Opera and Safari). | 446 * used only in Chrome, Opera and Safari). |
(...skipping 12 matching lines...) Expand all Loading... |
376 | 459 |
377 if (specificOnly && (!domains || domains[""])) | 460 if (specificOnly && (!domains || domains[""])) |
378 continue; | 461 continue; |
379 | 462 |
380 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) | 463 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) |
381 result.push(filter.selector); | 464 result.push(filter.selector); |
382 } | 465 } |
383 return result; | 466 return result; |
384 } | 467 } |
385 }; | 468 }; |
LEFT | RIGHT |