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

Side by Side Diff: lib/elemHide.js

Issue 29335650: Issue 2595 - Use the core code from adblockpluscore (Closed)
Patch Set: Created Feb. 4, 2016, 6:35 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 | « lib/downloader.js ('k') | lib/filterClasses.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH
4 *
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
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
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/>.
16 */
17
18 /**
19 * @fileOverview Element hiding implementation.
20 */
21
22 Cu.import("resource://gre/modules/Services.jsm");
23
24 var {Utils} = require("utils");
25 var {IO} = require("io");
26 var {Prefs} = require("prefs");
27 var {ElemHideException} = require("filterClasses");
28 var {FilterNotifier} = require("filterNotifier");
29
30 /**
31 * Lookup table, filters by their associated key
32 * @type Object
33 */
34 var filterByKey = Object.create(null);
35
36 /**
37 * Lookup table, keys of the filters by filter text
38 * @type Object
39 */
40 var keyByFilter = Object.create(null);
41
42 /**
43 * Lookup table, keys are known element hiding exceptions
44 * @type Object
45 */
46 var knownExceptions = Object.create(null);
47
48 /**
49 * Lookup table, lists of element hiding exceptions by selector
50 * @type Object
51 */
52 var exceptions = Object.create(null);
53
54 /**
55 * Currently applied stylesheet URL
56 * @type nsIURI
57 */
58 var styleURL = null;
59
60 /**
61 * Element hiding component
62 * @class
63 */
64 var ElemHide = exports.ElemHide =
65 {
66 /**
67 * Indicates whether filters have been added or removed since the last apply() call.
68 * @type Boolean
69 */
70 isDirty: false,
71
72 /**
73 * Inidicates whether the element hiding stylesheet is currently applied.
74 * @type Boolean
75 */
76 applied: false,
77
78 /**
79 * Called on module startup.
80 */
81 init: function()
82 {
83 Prefs.addListener(function(name)
84 {
85 if (name == "enabled")
86 ElemHide.apply();
87 });
88 onShutdown.add(() => ElemHide.unapply());
89
90 let styleFile = IO.resolveFilePath(Prefs.data_directory);
91 styleFile.append("elemhide.css");
92 styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL);
93 },
94
95 /**
96 * Removes all known filters
97 */
98 clear: function()
99 {
100 filterByKey = Object.create(null);
101 keyByFilter = Object.create(null);
102 knownExceptions = Object.create(null);
103 exceptions = Object.create(null);
104 ElemHide.isDirty = false;
105 ElemHide.unapply();
106 },
107
108 /**
109 * Add a new element hiding filter
110 * @param {ElemHideFilter} filter
111 */
112 add: function(filter)
113 {
114 if (filter instanceof ElemHideException)
115 {
116 if (filter.text in knownExceptions)
117 return;
118
119 let selector = filter.selector;
120 if (!(selector in exceptions))
121 exceptions[selector] = [];
122 exceptions[selector].push(filter);
123 knownExceptions[filter.text] = true;
124 }
125 else
126 {
127 if (filter.text in keyByFilter)
128 return;
129
130 let key;
131 do {
132 key = Math.random().toFixed(15).substr(5);
133 } while (key in filterByKey);
134
135 filterByKey[key] = filter;
136 keyByFilter[filter.text] = key;
137 ElemHide.isDirty = true;
138 }
139 },
140
141 /**
142 * Removes an element hiding filter
143 * @param {ElemHideFilter} filter
144 */
145 remove: function(filter)
146 {
147 if (filter instanceof ElemHideException)
148 {
149 if (!(filter.text in knownExceptions))
150 return;
151
152 let list = exceptions[filter.selector];
153 let index = list.indexOf(filter);
154 if (index >= 0)
155 list.splice(index, 1);
156 delete knownExceptions[filter.text];
157 }
158 else
159 {
160 if (!(filter.text in keyByFilter))
161 return;
162
163 let key = keyByFilter[filter.text];
164 delete filterByKey[key];
165 delete keyByFilter[filter.text];
166 ElemHide.isDirty = true;
167 }
168 },
169
170 /**
171 * Checks whether an exception rule is registered for a filter on a particular
172 * domain.
173 */
174 getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideE xception*/
175 {
176 if (!(filter.selector in exceptions))
177 return null;
178
179 let list = exceptions[filter.selector];
180 for (let i = list.length - 1; i >= 0; i--)
181 if (list[i].isActiveOnDomain(docDomain))
182 return list[i];
183
184 return null;
185 },
186
187 /**
188 * Will be set to true if apply() is running (reentrance protection).
189 * @type Boolean
190 */
191 _applying: false,
192
193 /**
194 * Will be set to true if an apply() call arrives while apply() is already
195 * running (delayed execution).
196 * @type Boolean
197 */
198 _needsApply: false,
199
200 /**
201 * Generates stylesheet URL and applies it globally
202 */
203 apply: function()
204 {
205 if (this._applying)
206 {
207 this._needsApply = true;
208 return;
209 }
210
211 if (!ElemHide.isDirty || !Prefs.enabled)
212 {
213 // Nothing changed, looks like we merely got enabled/disabled
214 if (Prefs.enabled && !ElemHide.applied)
215 {
216 try
217 {
218 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetServ ice.USER_SHEET);
219 ElemHide.applied = true;
220 }
221 catch (e)
222 {
223 Cu.reportError(e);
224 }
225 }
226 else if (!Prefs.enabled && ElemHide.applied)
227 {
228 ElemHide.unapply();
229 }
230
231 return;
232 }
233
234 IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e)
235 {
236 this._applying = false;
237
238 // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that
239 // there are no filters. If that exception is passed through XPCOM we will
240 // see a proper exception here, otherwise a number.
241 let noFilters = (e == Cr.NS_ERROR_NOT_AVAILABLE || (e && e.result == Cr.NS _ERROR_NOT_AVAILABLE));
242 if (noFilters)
243 {
244 e = null;
245 IO.removeFile(styleURL.file, function(e) {});
246 }
247 else if (e)
248 Cu.reportError(e);
249
250 if (this._needsApply)
251 {
252 this._needsApply = false;
253 this.apply();
254 }
255 else if (!e)
256 {
257 ElemHide.isDirty = false;
258
259 ElemHide.unapply();
260
261 if (!noFilters)
262 {
263 try
264 {
265 Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetSe rvice.USER_SHEET);
266 ElemHide.applied = true;
267 }
268 catch (e)
269 {
270 Cu.reportError(e);
271 }
272 }
273
274 FilterNotifier.triggerListeners("elemhideupdate");
275 }
276 }.bind(this));
277
278 this._applying = true;
279 },
280
281 _generateCSSContent: function*()
282 {
283 // Grouping selectors by domains
284 let domains = Object.create(null);
285 let hasFilters = false;
286 for (let key in filterByKey)
287 {
288 let filter = filterByKey[key];
289 let domain = filter.selectorDomain || "";
290
291 let list;
292 if (domain in domains)
293 list = domains[domain];
294 else
295 {
296 list = Object.create(null);
297 domains[domain] = list;
298 }
299 list[filter.selector] = key;
300 hasFilters = true;
301 }
302
303 if (!hasFilters)
304 throw Cr.NS_ERROR_NOT_AVAILABLE;
305
306 function escapeChar(match)
307 {
308 return "\\" + match.charCodeAt(0).toString(16) + " ";
309 }
310
311 // Return CSS data
312 let cssTemplate = "-moz-binding: url(about:abp-elemhidehit?%ID%#dummy) !impo rtant;";
313 for (let domain in domains)
314 {
315 let rules = [];
316 let list = domains[domain];
317
318 if (domain)
319 yield ('@-moz-document domain("' + domain.split(",").join('"),domain("') + '"){').replace(/[^\x01-\x7F]/g, escapeChar);
320 else
321 {
322 // Only allow unqualified rules on a few protocols to prevent them from blocking chrome
323 yield '@-moz-document url-prefix("http://"),url-prefix("https://"),'
324 + 'url-prefix("mailbox://"),url-prefix("imap://"),'
325 + 'url-prefix("news://"),url-prefix("snews://"){';
326 }
327
328 for (let selector in list)
329 yield selector.replace(/[^\x01-\x7F]/g, escapeChar) + "{" + cssTemplate. replace("%ID%", list[selector]) + "}";
330 yield '}';
331 }
332 },
333
334 /**
335 * Unapplies current stylesheet URL
336 */
337 unapply: function()
338 {
339 if (ElemHide.applied)
340 {
341 try
342 {
343 Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.USE R_SHEET);
344 }
345 catch (e)
346 {
347 Cu.reportError(e);
348 }
349 ElemHide.applied = false;
350 }
351 },
352
353 /**
354 * Retrieves the currently applied stylesheet URL
355 * @type String
356 */
357 get styleURL()
358 {
359 return ElemHide.applied ? styleURL.spec : null;
360 },
361
362 /**
363 * Retrieves an element hiding filter by the corresponding protocol key
364 */
365 getFilterByKey: function(/**String*/ key) /**Filter*/
366 {
367 return (key in filterByKey ? filterByKey[key] : null);
368 },
369
370 /**
371 * Returns a list of all selectors active on a particular domain (currently
372 * used only in Chrome, Opera and Safari).
373 */
374 getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly)
375 {
376 let result = [];
377 let keys = Object.getOwnPropertyNames(filterByKey);
378 for (let key of keys)
379 {
380 let filter = filterByKey[key];
381 if (specificOnly && (!filter.domains || filter.domains[""]))
382 continue;
383
384 if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain))
385 result.push(filter.selector);
386 }
387 return result;
388 }
389 };
OLDNEW
« no previous file with comments | « lib/downloader.js ('k') | lib/filterClasses.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld