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

Delta Between Two Patch Sets: lib/elemHide.js

Issue 6201308310667264: Issue 521- Inject our stylesheet on per-site basis rather than globally (Closed)
Left Patch Set: WIP Created June 4, 2014, 5:55 p.m.
Right Patch Set: Version 1.0 Created Aug. 6, 2014, 4:30 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 | « lib/contentPolicy.js ('k') | no next file » | 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
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
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
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
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 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld