Left: | ||
Right: |
OLD | NEW |
---|---|
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-2015 Eyeo GmbH | 3 * Copyright (C) 2006-2015 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 Content policy implementation, responsible for blocking things. | 19 * @fileOverview Content policy implementation, responsible for blocking things. |
20 */ | 20 */ |
21 | 21 |
22 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); | 22 "use strict"; |
23 Cu.import("resource://gre/modules/Services.jsm"); | 23 |
24 try | |
25 { | |
26 // Hack: SDK loader masks our Components object with a getter. | |
27 let proto = Object.getPrototypeOf(this); | |
28 let property = Object.getOwnPropertyDescriptor(proto, "Components"); | |
29 if (property && property.get) | |
30 delete proto.Components; | |
31 } | |
32 catch (e) | |
33 { | |
34 Cu.reportError(e); | |
35 } | |
36 | |
37 let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); | |
38 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); | |
24 | 39 |
25 let {Utils} = require("utils"); | 40 let {Utils} = require("utils"); |
26 let {Prefs} = require("prefs"); | 41 let {getWindow, getFrames, isPrivate} = require("child/utils"); |
27 let {FilterStorage} = require("filterStorage"); | |
28 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); | |
29 let {defaultMatcher} = require("matcher"); | |
30 let {objectMouseEventHander} = require("objectTabs"); | 42 let {objectMouseEventHander} = require("objectTabs"); |
31 let {RequestNotifier} = require("requestNotifier"); | 43 let {RequestNotifier} = require("requestNotifier"); |
32 let {ElemHide} = require("elemHide"); | |
33 | 44 |
34 /** | 45 /** |
35 * List of explicitly supported content types | 46 * Maps numerical content type IDs to strings. |
36 * @type string[] | 47 * @type Map |
37 */ | 48 */ |
38 let contentTypes = ["OTHER", "SCRIPT", "IMAGE", "STYLESHEET", "OBJECT", "SUBDOCU MENT", "DOCUMENT", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", "MEDIA"]; | 49 let types = new Map(); |
Wladimir Palant
2015/10/22 21:51:12
The original implementation used three maps for tr
| |
39 | 50 |
40 /** | 51 /** |
41 * List of content types that aren't associated with a visual document area | 52 * Numerical ID for fake ELEMHIDE type. |
42 * @type string[] | |
43 */ | 53 */ |
44 let nonVisualTypes = ["SCRIPT", "STYLESHEET", "XMLHTTPREQUEST", "OBJECT_SUBREQUE ST", "FONT"]; | 54 const TYPE_ELEMHIDE = 0xFFFD; |
55 | |
56 /** | |
57 * Numerical ID for fake POPUP type. | |
58 */ | |
59 const TYPE_POPUP = 0xFFFE; | |
45 | 60 |
46 /** | 61 /** |
47 * Randomly generated class name, to be applied to collapsed nodes. | 62 * Randomly generated class name, to be applied to collapsed nodes. |
63 * @type String | |
48 */ | 64 */ |
49 let collapsedClass = ""; | 65 let collapsedClass = null; |
50 | 66 |
51 /** | 67 /** |
52 * Public policy checking functions and auxiliary objects | 68 * Checks whether a request should be blocked, hides the corresponding element |
53 * @class | 69 * if necessary. |
70 * @param window {nsIDOMWindow} | |
71 * @param node {nsIDOMElement} | |
72 * @param contentType {Integer} | |
73 * @param location {String} | |
74 * @return {Boolean} true if the request should be blocked | |
54 */ | 75 */ |
55 var Policy = exports.Policy = | 76 function checkRequest(window, node, contentType, location) |
Wladimir Palant
2015/10/22 21:51:13
This is mostly equivalent to the old Policy.proces
| |
56 { | 77 { |
57 /** | 78 let response = sendSyncMessage("AdblockPlus:ShouldLoad", { |
58 * Map of content type identifiers by their name. | 79 contentType: types.get(contentType), |
59 * @type Object | 80 location: location, |
60 */ | 81 frames: getFrames(window), |
61 type: {}, | 82 isPrivate: isPrivate(window) |
83 }); | |
84 if (response.length == 0) | |
85 return false; | |
62 | 86 |
63 /** | 87 let {block, collapse, hits} = JSON.parse(response[0]); |
64 * Map of content type names by their identifiers (reverse of type map). | 88 for (let {frameIndex, contentType, docDomain, thirdParty, location, match} of hits) |
65 * @type Object | 89 { |
66 */ | 90 let context = node; |
67 typeDescr: {}, | 91 if (frameIndex >= 0) |
92 { | |
93 context = window; | |
94 for (let i = 0; i < frameIndex; i++) | |
95 context = context.parent; | |
96 context = context.document; | |
97 } | |
98 RequestNotifier.addNodeData(context, window.top, contentType, docDomain, thi rdParty, location, match); | |
99 } | |
68 | 100 |
69 /** | 101 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) |
70 * Map of numerical content types with their corresponding masks | |
71 * @type Object | |
72 */ | |
73 typeMask: {}, | |
74 | |
75 /** | |
76 * Map of localized content type names by their identifiers. | |
77 * @type Object | |
78 */ | |
79 localizedDescr: {}, | |
80 | |
81 /** | |
82 * Lists the non-visual content types. | |
83 * @type Object | |
84 */ | |
85 nonVisual: {}, | |
86 | |
87 /** | |
88 * Map containing all schemes that should be ignored by content policy. | |
89 * @type Object | |
90 */ | |
91 whitelistSchemes: {}, | |
92 | |
93 /** | |
94 * Called on module startup, initializes various exported properties. | |
95 */ | |
96 init: function() | |
97 { | 102 { |
98 // type constant by type description and type description by type constant | 103 // Track mouse events for objects |
99 let iface = Ci.nsIContentPolicy; | 104 if (!block && contentType == Ci.nsIContentPolicy.TYPE_OBJECT) |
100 for (let typeName of contentTypes) | |
101 { | 105 { |
102 if ("TYPE_" + typeName in iface) | 106 node.addEventListener("mouseover", objectMouseEventHander, true); |
103 { | 107 node.addEventListener("mouseout", objectMouseEventHander, true); |
104 let id = iface["TYPE_" + typeName]; | |
105 this.type[typeName] = id; | |
106 this.typeDescr[id] = typeName; | |
107 this.localizedDescr[id] = Utils.getString("type_label_" + typeName.toLow erCase()); | |
108 this.typeMask[id] = RegExpFilter.typeMap[typeName]; | |
109 } | |
110 } | 108 } |
111 | 109 |
112 this.type.ELEMHIDE = 0xFFFD; | 110 if (collapse) |
113 this.typeDescr[0xFFFD] = "ELEMHIDE"; | 111 schedulePostProcess(node); |
114 this.localizedDescr[0xFFFD] = Utils.getString("type_label_elemhide"); | 112 } |
115 this.typeMask[0xFFFD] = RegExpFilter.typeMap.ELEMHIDE; | |
116 | 113 |
117 this.type.POPUP = 0xFFFE; | 114 return block; |
118 this.typeDescr[0xFFFE] = "POPUP"; | 115 } |
119 this.localizedDescr[0xFFFE] = Utils.getString("type_label_popup"); | |
120 this.typeMask[0xFFFE] = RegExpFilter.typeMap.POPUP; | |
121 | |
122 for (let type of nonVisualTypes) | |
123 this.nonVisual[this.type[type]] = true; | |
124 | |
125 // whitelisted URL schemes | |
126 for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) | |
127 this.whitelistSchemes[scheme] = true; | |
128 | |
129 // Generate class identifier used to collapse node and register correspondin g | |
130 // stylesheet. | |
131 let offset = "a".charCodeAt(0); | |
132 for (let i = 0; i < 20; i++) | |
133 collapsedClass += String.fromCharCode(offset + Math.random() * 26); | |
134 | |
135 let collapseStyle = Services.io.newURI("data:text/css," + | |
136 encodeURIComponent("." + collapsedClass + | |
137 "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb azdummy) !important;}"), null, null); | |
138 Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi ce.USER_SHEET); | |
139 onShutdown.add(function() | |
140 { | |
141 Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService. USER_SHEET); | |
142 }); | |
143 }, | |
144 | |
145 /** | |
146 * Checks whether a node should be blocked, hides it if necessary | |
147 * @param wnd {nsIDOMWindow} | |
148 * @param node {nsIDOMElement} | |
149 * @param contentType {String} | |
150 * @param location {nsIURI} | |
151 * @param collapse {Boolean} true to force hiding of the node | |
152 * @return {Boolean} false if the node should be blocked | |
153 */ | |
154 processNode: function(wnd, node, contentType, location, collapse) | |
155 { | |
156 let topWnd = wnd.top; | |
157 if (!topWnd || !topWnd.location || !topWnd.location.href) | |
158 return true; | |
159 | |
160 let originWindow = Utils.getOriginWindow(wnd); | |
161 let wndLocation = originWindow.location.href; | |
162 let docDomain = getHostname(wndLocation); | |
163 let match = null; | |
164 let [sitekey, sitekeyWnd] = getSitekey(wnd); | |
165 let nogeneric = false; | |
166 | |
167 if (!match && Prefs.enabled) | |
168 { | |
169 let testWnd = wnd; | |
170 let testSitekey = sitekey; | |
171 let testSitekeyWnd = sitekeyWnd; | |
172 let parentWndLocation = getWindowLocation(testWnd); | |
173 while (true) | |
174 { | |
175 let testWndLocation = parentWndLocation; | |
176 parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWi ndowLocation(testWnd.parent)); | |
177 match = Policy.isWhitelisted(testWndLocation, parentWndLocation, testSit ekey); | |
178 | |
179 if (match instanceof WhitelistFilter) | |
180 { | |
181 FilterStorage.increaseHitCount(match, wnd); | |
182 RequestNotifier.addNodeData(testWnd.document, topWnd, Policy.type.DOCU MENT, getHostname(parentWndLocation), false, testWndLocation, match); | |
183 return true; | |
184 } | |
185 | |
186 let genericType = (contentType == Policy.type.ELEMHIDE ? | |
187 RegExpFilter.typeMap.GENERICHIDE : | |
188 RegExpFilter.typeMap.GENERICBLOCK); | |
189 let parentDocDomain = getHostname(parentWndLocation); | |
190 let nogenericMatch = defaultMatcher.matchesAny( | |
191 testWndLocation, genericType, parentDocDomain, false, testSitekey | |
192 ); | |
193 if (nogenericMatch instanceof WhitelistFilter) | |
194 { | |
195 nogeneric = true; | |
196 | |
197 FilterStorage.increaseHitCount(nogenericMatch, wnd); | |
198 RequestNotifier.addNodeData(testWnd.document, topWnd, contentType, | |
199 parentDocDomain, false, testWndLocation, | |
200 nogenericMatch); | |
201 } | |
202 | |
203 if (testWnd.parent == testWnd) | |
204 break; | |
205 | |
206 if (testWnd == testSitekeyWnd) | |
207 [testSitekey, testSitekeyWnd] = getSitekey(testWnd.parent); | |
208 testWnd = testWnd.parent; | |
209 } | |
210 } | |
211 | |
212 // Data loaded by plugins should be attached to the document | |
213 if (contentType == Policy.type.OBJECT_SUBREQUEST && node instanceof Ci.nsIDO MElement) | |
214 node = node.ownerDocument; | |
215 | |
216 // Fix type for objects misrepresented as frames or images | |
217 if (contentType != Policy.type.OBJECT && (node instanceof Ci.nsIDOMHTMLObjec tElement || node instanceof Ci.nsIDOMHTMLEmbedElement)) | |
218 contentType = Policy.type.OBJECT; | |
219 | |
220 let locationText = location.spec; | |
221 if (!match && contentType == Policy.type.ELEMHIDE) | |
222 { | |
223 let testWnd = wnd; | |
224 let parentWndLocation = getWindowLocation(testWnd); | |
225 while (true) | |
226 { | |
227 let testWndLocation = parentWndLocation; | |
228 parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWi ndowLocation(testWnd.parent)); | |
229 let parentDocDomain = getHostname(parentWndLocation); | |
230 match = defaultMatcher.matchesAny(testWndLocation, RegExpFilter.typeMap. ELEMHIDE, parentDocDomain, false, sitekey); | |
231 if (match instanceof WhitelistFilter) | |
232 { | |
233 FilterStorage.increaseHitCount(match, wnd); | |
234 RequestNotifier.addNodeData(testWnd.document, topWnd, contentType, par entDocDomain, false, testWndLocation, match); | |
235 return true; | |
236 } | |
237 | |
238 if (testWnd.parent == testWnd) | |
239 break; | |
240 else | |
241 testWnd = testWnd.parent; | |
242 } | |
243 | |
244 match = location; | |
245 locationText = match.text.replace(/^.*?#/, '#'); | |
246 location = locationText; | |
247 | |
248 if (!match.isActiveOnDomain(docDomain)) | |
249 return true; | |
250 | |
251 let exception = ElemHide.getException(match, docDomain); | |
252 if (exception) | |
253 { | |
254 FilterStorage.increaseHitCount(exception, wnd); | |
255 RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, false, locationText, exception); | |
256 return true; | |
257 } | |
258 | |
259 if (nogeneric && match.isGeneric()) | |
260 return true; | |
261 } | |
262 | |
263 let thirdParty = (contentType == Policy.type.ELEMHIDE ? false : isThirdParty (location, docDomain)); | |
264 | |
265 if (!match && Prefs.enabled && contentType in Policy.typeMask) | |
266 { | |
267 match = defaultMatcher.matchesAny(locationText, Policy.typeMask[contentTyp e], | |
268 docDomain, thirdParty, sitekey, nogeneri c); | |
269 if (match instanceof BlockingFilter && node.ownerDocument && !(contentType in Policy.nonVisual)) | |
270 { | |
271 let prefCollapse = (match.collapse != null ? match.collapse : !Prefs.fas tcollapse); | |
272 if (collapse || prefCollapse) | |
273 schedulePostProcess(node); | |
274 } | |
275 | |
276 // Track mouse events for objects | |
277 if (!match && contentType == Policy.type.OBJECT && node.nodeType == Ci.nsI DOMNode.ELEMENT_NODE) | |
278 { | |
279 node.addEventListener("mouseover", objectMouseEventHander, true); | |
280 node.addEventListener("mouseout", objectMouseEventHander, true); | |
281 } | |
282 } | |
283 | |
284 // Store node data | |
285 RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, thirdParty , locationText, match); | |
286 if (match) | |
287 FilterStorage.increaseHitCount(match, wnd); | |
288 | |
289 return !match || match instanceof WhitelistFilter; | |
290 }, | |
291 | |
292 /** | |
293 * Checks whether the location's scheme is blockable. | |
294 * @param location {nsIURI} | |
295 * @return {Boolean} | |
296 */ | |
297 isBlockableScheme: function(location) | |
298 { | |
299 return !(location.scheme in Policy.whitelistSchemes); | |
300 }, | |
301 | |
302 /** | |
303 * Checks whether a page is whitelisted. | |
304 * @param {String} url | |
305 * @param {String} [parentUrl] location of the parent page | |
306 * @param {String} [sitekey] public key provided on the page | |
307 * @return {Filter} filter that matched the URL or null if not whitelisted | |
308 */ | |
309 isWhitelisted: function(url, parentUrl, sitekey) | |
310 { | |
311 if (!url) | |
312 return null; | |
313 | |
314 // Do not apply exception rules to schemes on our whitelistschemes list. | |
315 let match = /^([\w\-]+):/.exec(url); | |
316 if (match && match[1] in Policy.whitelistSchemes) | |
317 return null; | |
318 | |
319 if (!parentUrl) | |
320 parentUrl = url; | |
321 | |
322 // Ignore fragment identifier | |
323 let index = url.indexOf("#"); | |
324 if (index >= 0) | |
325 url = url.substring(0, index); | |
326 | |
327 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g etHostname(parentUrl), false, sitekey); | |
328 return (result instanceof WhitelistFilter ? result : null); | |
329 }, | |
330 | |
331 /** | |
332 * Checks whether the page loaded in a window is whitelisted for indication in the UI. | |
333 * @param wnd {nsIDOMWindow} | |
334 * @return {Filter} matching exception rule or null if not whitelisted | |
335 */ | |
336 isWindowWhitelisted: function(wnd) | |
337 { | |
338 return Policy.isWhitelisted(getWindowLocation(wnd)); | |
339 }, | |
340 | |
341 /** | |
342 * Asynchronously re-checks filters for given nodes. | |
343 * @param {Node[]} nodes | |
344 * @param {RequestEntry} entry | |
345 */ | |
346 refilterNodes: function(nodes, entry) | |
347 { | |
348 // Ignore nodes that have been blocked already | |
349 if (entry.filter && !(entry.filter instanceof WhitelistFilter)) | |
350 return; | |
351 | |
352 for (let node of nodes) | |
353 Utils.runAsync(() => refilterNode(node, entry)); | |
354 } | |
355 }; | |
356 Policy.init(); | |
357 | 116 |
358 /** | 117 /** |
359 * Actual nsIContentPolicy and nsIChannelEventSink implementation | 118 * Actual nsIContentPolicy and nsIChannelEventSink implementation |
360 * @class | 119 * @class |
361 */ | 120 */ |
362 var PolicyImplementation = | 121 var PolicyImplementation = |
363 { | 122 { |
364 classDescription: "Adblock Plus content policy", | 123 classDescription: "Adblock Plus content policy", |
365 classID: Components.ID("cfeaabe6-1dd1-11b2-a0c6-cb5c268894c9"), | 124 classID: Components.ID("cfeaabe6-1dd1-11b2-a0c6-cb5c268894c9"), |
366 contractID: "@adblockplus.org/abp/policy;1", | 125 contractID: "@adblockplus.org/abp/policy;1", |
367 xpcom_categories: ["content-policy", "net-channel-event-sinks"], | 126 xpcom_categories: ["content-policy", "net-channel-event-sinks"], |
368 | 127 |
369 /** | 128 /** |
370 * Registers the content policy on startup. | 129 * Registers the content policy on startup. |
371 */ | 130 */ |
372 init: function() | 131 init: function() |
373 { | 132 { |
133 // Populate types map | |
134 let iface = Ci.nsIContentPolicy; | |
135 for (let name in iface) | |
136 if (name.indexOf("TYPE_") == 0) | |
137 types.set(iface[name], name.substr(5)); | |
138 types.set(TYPE_ELEMHIDE, "ELEMHIDE"); | |
139 types.set(TYPE_POPUP, "POPUP"); | |
140 | |
374 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); | 141 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); |
375 try | 142 registrar.registerFactory(this.classID, this.classDescription, this.contract ID, this); |
376 { | |
377 registrar.registerFactory(this.classID, this.classDescription, this.contra ctID, this); | |
378 } | |
379 catch (e) | |
380 { | |
381 // See bug 924340 - it might be too early to init now, the old version | |
382 // we are replacing didn't finish removing itself yet. | |
383 if (e.result == Cr.NS_ERROR_FACTORY_EXISTS) | |
384 { | |
385 Utils.runAsync(this.init.bind(this)); | |
386 return; | |
387 } | |
388 | |
389 Cu.reportError(e); | |
390 } | |
391 | 143 |
Wladimir Palant
2015/10/22 21:51:13
Bug 924340 has been fixed in Firefox 26, bug 75368
| |
392 let catMan = Utils.categoryManager; | 144 let catMan = Utils.categoryManager; |
145 let addCategoryEntry = Utils.getPropertyWithoutCompatShims(catMan, "addCateg oryEntry"); | |
393 for (let category of this.xpcom_categories) | 146 for (let category of this.xpcom_categories) |
394 catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true); | 147 addCategoryEntry.call(catMan, category, this.contractID, this.contractID, false, true); |
395 | 148 |
396 // http-on-opening-request is new in Gecko 18, http-on-modify-request can | 149 let addObserver = Utils.getPropertyWithoutCompatShims(Services.obs, "addObse rver"); |
397 // be used in earlier releases. | 150 addObserver.call(Services.obs, this, "content-document-global-created", true ); |
398 let httpTopic = "http-on-opening-request"; | |
399 if (Services.vc.compare(Utils.platformVersion, "18.0") < 0) | |
400 httpTopic = "http-on-modify-request"; | |
401 | 151 |
402 Services.obs.addObserver(this, httpTopic, true); | 152 onShutdown.add(() => |
403 Services.obs.addObserver(this, "content-document-global-created", true); | |
404 Services.obs.addObserver(this, "xpcom-category-entry-removed", true); | |
405 Services.obs.addObserver(this, "xpcom-category-cleared", true); | |
406 | |
407 onShutdown.add(function() | |
408 { | 153 { |
409 // Our category observers should be removed before changing category | 154 // Our category observers should be removed before changing category |
410 // memberships, just in case. | 155 // memberships, just in case. |
411 Services.obs.removeObserver(this, httpTopic); | 156 let removeObserver = Utils.getPropertyWithoutCompatShims(Services.obs, "re moveObserver"); |
412 Services.obs.removeObserver(this, "content-document-global-created"); | 157 removeObserver.call(Services.obs, this, "content-document-global-created") ; |
413 Services.obs.removeObserver(this, "xpcom-category-entry-removed"); | |
414 Services.obs.removeObserver(this, "xpcom-category-cleared"); | |
415 | 158 |
416 for (let category of this.xpcom_categories) | 159 for (let category of this.xpcom_categories) |
417 catMan.deleteCategoryEntry(category, this.contractID, false); | 160 catMan.deleteCategoryEntry(category, this.contractID, false); |
418 | 161 |
419 // This needs to run asynchronously, see bug 753687 | 162 registrar.unregisterFactory(this.classID, this); |
420 Utils.runAsync(function() | 163 }); |
421 { | |
422 registrar.unregisterFactory(this.classID, this); | |
423 }.bind(this)); | |
424 | 164 |
425 this.previousRequest = null; | 165 let setCollapsedClass = (message) => { |
426 }.bind(this)); | 166 collapsedClass = message.data; |
167 removeMessageListener("AdblockPlus:CollapsedClass", setCollapsedClass); | |
168 }; | |
169 addMessageListener("AdblockPlus:CollapsedClass", setCollapsedClass); | |
Wladimir Palant
2015/10/22 21:51:12
This class name has to be identical in all process
| |
427 }, | 170 }, |
428 | 171 |
429 // | 172 // |
430 // nsISupports interface implementation | 173 // nsISupports interface implementation |
431 // | 174 // |
432 | 175 |
433 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, | 176 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, |
434 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), | 177 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), |
435 | 178 |
436 // | 179 // |
437 // nsIContentPolicy interface implementation | 180 // nsIContentPolicy interface implementation |
438 // | 181 // |
439 | 182 |
440 shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTy peGuess, extra) | 183 shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTy peGuess, extra) |
441 { | 184 { |
442 // Ignore requests without context and top-level documents | 185 // Ignore requests without context and top-level documents |
443 if (!node || contentType == Policy.type.DOCUMENT) | 186 if (!node || contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) |
444 return Ci.nsIContentPolicy.ACCEPT; | 187 return Ci.nsIContentPolicy.ACCEPT; |
445 | 188 |
446 // Ignore standalone objects | 189 // Ignore standalone objects |
447 if (contentType == Policy.type.OBJECT && node.ownerDocument && !/^text\/|[+\ /]xml$/.test(node.ownerDocument.contentType)) | 190 if (contentType == Ci.nsIContentPolicy.TYPE_OBJECT && node.ownerDocument && !/^text\/|[+\/]xml$/.test(node.ownerDocument.contentType)) |
448 return Ci.nsIContentPolicy.ACCEPT; | 191 return Ci.nsIContentPolicy.ACCEPT; |
449 | 192 |
450 let wnd = Utils.getWindow(node); | 193 let window = getWindow(node); |
Wladimir Palant
2015/10/22 21:51:12
I'm not using Utils.getWindow() here for a reason
| |
451 if (!wnd) | 194 if (!window) |
452 return Ci.nsIContentPolicy.ACCEPT; | 195 return Ci.nsIContentPolicy.ACCEPT; |
453 | 196 |
454 // Ignore whitelisted schemes | 197 // Fix type for objects misrepresented as frames or images |
455 let location = Utils.unwrapURL(contentLocation); | 198 if (contentType != Ci.nsIContentPolicy.TYPE_OBJECT && (node instanceof Ci.ns IDOMHTMLObjectElement || node instanceof Ci.nsIDOMHTMLEmbedElement)) |
456 if (!Policy.isBlockableScheme(location)) | 199 contentType = Ci.nsIContentPolicy.TYPE_OBJECT; |
457 return Ci.nsIContentPolicy.ACCEPT; | |
458 | 200 |
459 // Interpret unknown types as "other" | 201 // Data loaded by plugins should be attached to the document |
460 if (!(contentType in Policy.typeDescr)) | 202 if (contentType == Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST && node instan ceof Ci.nsIDOMElement) |
461 contentType = Policy.type.OTHER; | 203 node = node.ownerDocument; |
Wladimir Palant
2015/10/22 21:51:13
The checks had to be shuffled around somewhat. Che
| |
462 | 204 |
463 let result = Policy.processNode(wnd, node, contentType, location, false); | 205 let block = checkRequest(window, node, contentType, contentLocation.spec); |
464 if (result) | 206 return (block ? Ci.nsIContentPolicy.REJECT_REQUEST : Ci.nsIContentPolicy.ACC EPT); |
465 { | |
466 // We didn't block this request so we will probably see it again in | |
467 // http-on-opening-request. Keep it so that we can associate it with the | |
468 // channel there - will be needed in case of redirect. | |
469 this.previousRequest = [location, contentType]; | |
470 } | |
471 return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQ UEST); | |
472 }, | 207 }, |
473 | 208 |
474 shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode , mimeType, extra) | 209 shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode , mimeType, extra) |
475 { | 210 { |
476 return Ci.nsIContentPolicy.ACCEPT; | 211 return Ci.nsIContentPolicy.ACCEPT; |
477 }, | 212 }, |
478 | 213 |
479 // | 214 // |
480 // nsIObserver interface implementation | 215 // nsIObserver interface implementation |
481 // | 216 // |
482 observe: function(subject, topic, data, additional) | 217 observe: function(subject, topic, data, additional) |
483 { | 218 { |
484 switch (topic) | 219 switch (topic) |
485 { | 220 { |
486 case "content-document-global-created": | 221 case "content-document-global-created": |
487 { | 222 { |
488 if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener) | 223 if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener) |
489 return; | 224 return; |
490 | 225 |
491 let uri = additional || Utils.makeURI(subject.location.href); | 226 let uri = additional || subject.location.href; |
492 if (!Policy.processNode(subject.opener, subject.opener.document, Policy. type.POPUP, uri, false)) | 227 if (checkRequest(subject.opener, subject.opener.document, TYPE_POPUP, ur i)) |
493 { | 228 { |
494 subject.stop(); | 229 subject.stop(); |
495 Utils.runAsync(() => subject.close()); | 230 Utils.runAsync(() => subject.close()); |
496 } | 231 } |
497 else if (uri.spec == "about:blank") | 232 else if (uri == "about:blank") |
498 { | 233 { |
499 // An about:blank pop-up most likely means that a load will be | 234 // An about:blank pop-up most likely means that a load will be |
500 // initiated synchronously. Set a flag for our "http-on-opening-reques t" | 235 // initiated asynchronously. Wait for that. |
501 // handler. | 236 Utils.runAsync(() => |
502 this.expectingPopupLoad = true; | |
503 Utils.runAsync(function() | |
504 { | 237 { |
505 this.expectingPopupLoad = false; | 238 let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor) |
239 .getInterface(Ci.nsIDocShell) | |
240 .QueryInterface(Ci.nsIDocumentLoader) | |
241 .documentChannel; | |
242 if (channel) | |
243 this.observe(subject, topic, data, channel.URI.spec); | |
Wladimir Palant
2015/10/22 21:51:13
This relied on the http-on-opening-request notific
| |
506 }); | 244 }); |
507 } | 245 } |
508 break; | 246 break; |
509 } | 247 } |
510 case "http-on-opening-request": | |
511 case "http-on-modify-request": | |
512 { | |
513 if (!(subject instanceof Ci.nsIHttpChannel)) | |
514 return; | |
515 | |
516 if (this.previousRequest && subject.URI == this.previousRequest[0] && | |
517 subject instanceof Ci.nsIWritablePropertyBag) | |
518 { | |
519 // We just handled a content policy call for this request - associate | |
520 // the data with the channel so that we can find it in case of a redir ect. | |
521 subject.setProperty("abpRequestType", this.previousRequest[1]); | |
522 this.previousRequest = null; | |
523 } | |
524 | |
525 if (this.expectingPopupLoad) | |
526 { | |
527 let wnd = Utils.getRequestWindow(subject); | |
528 if (wnd && wnd.opener && wnd.location.href == "about:blank") | |
529 { | |
530 this.observe(wnd, "content-document-global-created", null, subject.U RI); | |
531 if (subject instanceof Ci.nsIWritablePropertyBag) | |
532 subject.setProperty("abpRequestType", Policy.type.POPUP); | |
533 } | |
534 } | |
535 | |
536 break; | |
537 } | |
538 case "xpcom-category-entry-removed": | |
539 case "xpcom-category-cleared": | |
540 { | |
541 let category = data; | |
542 if (this.xpcom_categories.indexOf(category) < 0) | |
543 return; | |
544 | |
545 if (topic == "xpcom-category-entry-removed" && | |
546 subject instanceof Ci.nsISupportsCString && | |
547 subject.data != this.contractID) | |
548 { | |
549 return; | |
550 } | |
551 | |
552 // Our category entry was removed, make sure to add it back | |
553 let catMan = Utils.categoryManager; | |
554 catMan.addCategoryEntry(category, this.contractID, this.contractID, fals e, true); | |
555 break; | |
556 } | |
Wladimir Palant
2015/10/22 21:51:13
The code above was added because of AVG Secure Sea
| |
557 } | 248 } |
558 }, | 249 }, |
559 | 250 |
560 // | 251 // |
561 // nsIChannelEventSink interface implementation | 252 // nsIChannelEventSink interface implementation |
562 // | 253 // |
563 | 254 |
564 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) | 255 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) |
565 { | 256 { |
566 let result = Cr.NS_OK; | 257 let result = Cr.NS_OK; |
567 try | 258 try |
568 { | 259 { |
569 // Try to retrieve previously stored request data from the channel | 260 // nsILoadInfo.contentPolicyType was introduced in Gecko 35, then |
570 let contentType; | 261 // renamed to nsILoadInfo.externalContentPolicyType in Gecko 44. |
571 if (oldChannel instanceof Ci.nsIWritablePropertyBag) | 262 let loadInfo = oldChannel.loadInfo; |
263 let contentType = loadInfo.externalContentPolicyType || loadInfo.contentPo licyType; | |
264 if (!contentType) | |
265 return; | |
Wladimir Palant
2015/10/22 21:51:13
Originally we used a complicated and rather fragil
| |
266 | |
267 let wnd = Utils.getRequestWindow(newChannel); | |
268 if (!wnd) | |
269 return; | |
270 | |
271 if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) | |
572 { | 272 { |
573 try | 273 if (wnd.history.length <= 1 && wnd.opener) |
Wladimir Palant
2015/10/22 21:51:13
Since we no longer attach our content type to the
| |
574 { | 274 { |
575 contentType = oldChannel.getProperty("abpRequestType"); | 275 // Special treatment for pop-up windows |
276 this.observe(wnd, "content-document-global-created", null); | |
Wladimir Palant
2015/10/22 21:51:13
This fixes a bug in the original code. It was mere
| |
576 } | 277 } |
577 catch(e) | 278 return; |
578 { | |
579 // No data attached, ignore this redirect | |
580 return; | |
581 } | |
582 } | 279 } |
583 | 280 |
584 let newLocation = null; | 281 let newLocation = null; |
585 try | 282 try |
586 { | 283 { |
587 newLocation = newChannel.URI; | 284 newLocation = newChannel.URI; |
588 } catch(e2) {} | 285 } catch(e2) {} |
589 if (!newLocation) | 286 if (!newLocation) |
590 return; | 287 return; |
591 | 288 |
592 let wnd = Utils.getRequestWindow(newChannel); | 289 if (checkRequest(wnd, wnd.document, contentType, newLocation.spec)) |
593 if (!wnd) | |
594 return; | |
595 | |
596 if (contentType == Policy.type.SUBDOCUMENT && wnd.parent == wnd.top && wnd .opener) | |
597 { | |
598 // This is a window opened in a new tab miscategorized as frame load, | |
599 // see bug 467514. Get the frame as context to be at least consistent. | |
600 wnd = wnd.opener; | |
601 } | |
Wladimir Palant
2015/10/22 21:51:12
In bug 467514 I confirmed that the issue is gone i
| |
602 | |
603 if (contentType == Policy.type.POPUP && wnd.opener) | |
604 { | |
605 // Popups are initiated by their opener, not their own window. | |
606 wnd = wnd.opener; | |
607 } | |
608 | |
609 if (!Policy.processNode(wnd, wnd.document, contentType, newLocation, false )) | |
610 result = Cr.NS_BINDING_ABORTED; | 290 result = Cr.NS_BINDING_ABORTED; |
611 } | 291 } |
612 catch (e) | 292 catch (e) |
613 { | 293 { |
614 // We shouldn't throw exceptions here - this will prevent the redirect. | 294 // We shouldn't throw exceptions here - this will prevent the redirect. |
615 Cu.reportError(e); | 295 Cu.reportError(e); |
616 } | 296 } |
617 finally | 297 finally |
618 { | 298 { |
619 callback.onRedirectVerifyCallback(result); | 299 callback.onRedirectVerifyCallback(result); |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
651 scheduledNodes = [node]; | 331 scheduledNodes = [node]; |
652 Utils.runAsync(postProcessNodes); | 332 Utils.runAsync(postProcessNodes); |
653 } | 333 } |
654 } | 334 } |
655 | 335 |
656 /** | 336 /** |
657 * Processes nodes scheduled for post-processing (typically hides them). | 337 * Processes nodes scheduled for post-processing (typically hides them). |
658 */ | 338 */ |
659 function postProcessNodes() | 339 function postProcessNodes() |
660 { | 340 { |
341 if (!collapsedClass) | |
342 { | |
343 Utils.runAsync(postProcessNodes); | |
344 return; | |
345 } | |
346 | |
661 let nodes = scheduledNodes; | 347 let nodes = scheduledNodes; |
662 scheduledNodes = null; | 348 scheduledNodes = null; |
663 | 349 |
664 for (let node of nodes) | 350 for (let node of nodes) |
665 { | 351 { |
666 // adjust frameset's cols/rows for frames | 352 // adjust frameset's cols/rows for frames |
667 let parentNode = node.parentNode; | 353 let parentNode = node.parentNode; |
668 if (parentNode && parentNode instanceof Ci.nsIDOMHTMLFrameSetElement) | 354 if (parentNode && parentNode instanceof Ci.nsIDOMHTMLFrameSetElement) |
669 { | 355 { |
670 let hasCols = (parentNode.cols && parentNode.cols.indexOf(",") > 0); | 356 let hasCols = (parentNode.cols && parentNode.cols.indexOf(",") > 0); |
671 let hasRows = (parentNode.rows && parentNode.rows.indexOf(",") > 0); | 357 let hasRows = (parentNode.rows && parentNode.rows.indexOf(",") > 0); |
672 if ((hasCols || hasRows) && !(hasCols && hasRows)) | 358 if ((hasCols || hasRows) && !(hasCols && hasRows)) |
673 { | 359 { |
674 let index = -1; | 360 let index = -1; |
675 for (let frame = node; frame; frame = frame.previousSibling) | 361 for (let frame = node; frame; frame = frame.previousSibling) |
676 if (frame instanceof Ci.nsIDOMHTMLFrameElement || frame instanceof Ci. nsIDOMHTMLFrameSetElement) | 362 if (frame instanceof Ci.nsIDOMHTMLFrameElement || frame instanceof Ci. nsIDOMHTMLFrameSetElement) |
677 index++; | 363 index++; |
678 | 364 |
679 let property = (hasCols ? "cols" : "rows"); | 365 let property = (hasCols ? "cols" : "rows"); |
680 let weights = parentNode[property].split(","); | 366 let weights = parentNode[property].split(","); |
681 weights[index] = "0"; | 367 weights[index] = "0"; |
682 parentNode[property] = weights.join(","); | 368 parentNode[property] = weights.join(","); |
683 } | 369 } |
684 } | 370 } |
685 else | 371 else |
686 node.classList.add(collapsedClass); | 372 node.classList.add(collapsedClass); |
687 } | 373 } |
688 } | 374 } |
689 | |
690 /** | |
691 * Extracts the hostname from a URL (might return null). | |
692 */ | |
693 function getHostname(/**String*/ url) /**String*/ | |
694 { | |
695 try | |
696 { | |
697 return Utils.unwrapURL(url).host; | |
698 } | |
699 catch(e) | |
700 { | |
701 return null; | |
702 } | |
703 } | |
704 | |
705 /** | |
706 * Retrieves the sitekey of a window. | |
707 */ | |
708 function getSitekey(wnd) | |
709 { | |
710 let sitekey = null; | |
711 | |
712 while (true) | |
713 { | |
714 if (wnd.document && wnd.document.documentElement) | |
715 { | |
716 let keydata = wnd.document.documentElement.getAttribute("data-adblockkey") ; | |
717 if (keydata && keydata.indexOf("_") >= 0) | |
718 { | |
719 let [key, signature] = keydata.split("_", 2); | |
720 key = key.replace(/=/g, ""); | |
721 | |
722 // Website specifies a key but is the signature valid? | |
723 let uri = Services.io.newURI(getWindowLocation(wnd), null, null); | |
724 let host = uri.asciiHost; | |
725 if (uri.port > 0) | |
726 host += ":" + uri.port; | |
727 let params = [ | |
728 uri.path.replace(/#.*/, ""), // REQUEST_URI | |
729 host, // HTTP_HOST | |
730 Utils.httpProtocol.userAgent // HTTP_USER_AGENT | |
731 ]; | |
732 if (Utils.verifySignature(key, signature, params.join("\0"))) | |
733 return [key, wnd]; | |
734 } | |
735 } | |
736 | |
737 if (wnd === wnd.parent) | |
738 break; | |
739 | |
740 wnd = wnd.parent; | |
741 } | |
742 | |
743 return [sitekey, wnd]; | |
744 } | |
745 | |
746 /** | |
747 * Retrieves the location of a window. | |
748 * @param wnd {nsIDOMWindow} | |
749 * @return {String} window location or null on failure | |
750 */ | |
751 function getWindowLocation(wnd) | |
752 { | |
753 if ("name" in wnd && wnd.name == "messagepane") | |
754 { | |
755 // Thunderbird branch | |
756 try | |
757 { | |
758 let mailWnd = wnd.QueryInterface(Ci.nsIInterfaceRequestor) | |
759 .getInterface(Ci.nsIWebNavigation) | |
760 .QueryInterface(Ci.nsIDocShellTreeItem) | |
761 .rootTreeItem | |
762 .QueryInterface(Ci.nsIInterfaceRequestor) | |
763 .getInterface(Ci.nsIDOMWindow); | |
764 | |
765 // Typically we get a wrapped mail window here, need to unwrap | |
766 try | |
767 { | |
768 mailWnd = mailWnd.wrappedJSObject; | |
769 } catch(e) {} | |
770 | |
771 if ("currentHeaderData" in mailWnd && "content-base" in mailWnd.currentHea derData) | |
772 { | |
773 return mailWnd.currentHeaderData["content-base"].headerValue; | |
774 } | |
775 else if ("currentHeaderData" in mailWnd && "from" in mailWnd.currentHeader Data) | |
776 { | |
777 let emailAddress = Utils.headerParser.extractHeaderAddressMailboxes(mail Wnd.currentHeaderData.from.headerValue); | |
778 if (emailAddress) | |
779 return 'mailto:' + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+ $/, "").replace(/\s/g, '%20'); | |
780 } | |
781 } catch(e) {} | |
782 } | |
783 | |
784 // Firefox branch | |
785 return wnd.location.href; | |
786 } | |
787 | |
788 /** | |
789 * Checks whether the location's origin is different from document's origin. | |
790 */ | |
791 function isThirdParty(/**nsIURI*/location, /**String*/ docDomain) /**Boolean*/ | |
792 { | |
793 if (!location || !docDomain) | |
794 return true; | |
795 | |
796 try | |
797 { | |
798 return Utils.effectiveTLD.getBaseDomain(location) != Utils.effectiveTLD.getB aseDomainFromHost(docDomain); | |
799 } | |
800 catch (e) | |
801 { | |
802 // EffectiveTLDService throws on IP addresses, just compare the host name | |
803 let host = ""; | |
804 try | |
805 { | |
806 host = location.host; | |
807 } catch (e) {} | |
808 return host != docDomain; | |
809 } | |
810 } | |
811 | |
812 /** | |
813 * Re-checks filters on an element. | |
814 */ | |
815 function refilterNode(/**Node*/ node, /**RequestEntry*/ entry) | |
816 { | |
817 let wnd = Utils.getWindow(node); | |
818 if (!wnd || wnd.closed) | |
819 return; | |
820 | |
821 if (entry.type == Policy.type.OBJECT) | |
822 { | |
823 node.removeEventListener("mouseover", objectMouseEventHander, true); | |
824 node.removeEventListener("mouseout", objectMouseEventHander, true); | |
825 } | |
826 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true) ; | |
827 } | |
OLD | NEW |