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

Delta Between Two Patch Sets: lib/contentPolicy.js

Issue 29329419: Issue 3208 - Unify handling of $document and $elemhide exceptions and improve performance (Closed)
Left Patch Set: Created Oct. 27, 2015, 2:28 p.m.
Right Patch Set: Fixed sitekey parameter Created Nov. 11, 2015, 7:53 a.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 | « no previous file | 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 <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 let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
25 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
24 26
25 let {Utils} = require("utils"); 27 let {Utils} = require("utils");
26 let {Prefs} = require("prefs"); 28 let {Prefs} = require("prefs");
27 let {FilterStorage} = require("filterStorage"); 29 let {FilterStorage} = require("filterStorage");
28 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); 30 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses");
29 let {defaultMatcher} = require("matcher"); 31 let {defaultMatcher} = require("matcher");
30 let {objectMouseEventHander} = require("objectTabs"); 32 let {objectMouseEventHander} = require("objectTabs");
31 let {RequestNotifier} = require("requestNotifier"); 33 let {RequestNotifier} = require("requestNotifier");
32 let {ElemHide} = require("elemHide"); 34 let {ElemHide} = require("elemHide");
33 35
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 localizedDescr: {}, 81 localizedDescr: {},
80 82
81 /** 83 /**
82 * Lists the non-visual content types. 84 * Lists the non-visual content types.
83 * @type Object 85 * @type Object
84 */ 86 */
85 nonVisual: {}, 87 nonVisual: {},
86 88
87 /** 89 /**
88 * Map containing all schemes that should be ignored by content policy. 90 * Map containing all schemes that should be ignored by content policy.
89 * @type Object 91 * @type Set
90 */ 92 */
91 whitelistSchemes: {}, 93 whitelistSchemes: new Set(),
92 94
93 /** 95 /**
94 * Called on module startup, initializes various exported properties. 96 * Called on module startup, initializes various exported properties.
95 */ 97 */
96 init: function() 98 init: function()
97 { 99 {
98 // type constant by type description and type description by type constant 100 // type constant by type description and type description by type constant
99 let iface = Ci.nsIContentPolicy; 101 let iface = Ci.nsIContentPolicy;
100 for (let typeName of contentTypes) 102 for (let typeName of contentTypes)
101 { 103 {
(...skipping 25 matching lines...) Expand all
127 this.type.POPUP = 0xFFFE; 129 this.type.POPUP = 0xFFFE;
128 this.typeDescr[0xFFFE] = "POPUP"; 130 this.typeDescr[0xFFFE] = "POPUP";
129 this.localizedDescr[0xFFFE] = Utils.getString("type_label_popup"); 131 this.localizedDescr[0xFFFE] = Utils.getString("type_label_popup");
130 this.typeMask[0xFFFE] = RegExpFilter.typeMap.POPUP; 132 this.typeMask[0xFFFE] = RegExpFilter.typeMap.POPUP;
131 133
132 for (let type of nonVisualTypes) 134 for (let type of nonVisualTypes)
133 this.nonVisual[this.type[type]] = true; 135 this.nonVisual[this.type[type]] = true;
134 136
135 // whitelisted URL schemes 137 // whitelisted URL schemes
136 for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) 138 for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" "))
137 this.whitelistSchemes[scheme] = true; 139 this.whitelistSchemes.add(scheme);
138 140
139 // Generate class identifier used to collapse node and register correspondin g 141 // Generate class identifier used to collapse node and register correspondin g
140 // stylesheet. 142 // stylesheet.
141 let offset = "a".charCodeAt(0); 143 let offset = "a".charCodeAt(0);
142 for (let i = 0; i < 20; i++) 144 for (let i = 0; i < 20; i++)
143 collapsedClass += String.fromCharCode(offset + Math.random() * 26); 145 collapsedClass += String.fromCharCode(offset + Math.random() * 26);
144 146
145 let collapseStyle = Services.io.newURI("data:text/css," + 147 let collapseStyle = Services.io.newURI("data:text/css," +
146 encodeURIComponent("." + collapsedClass + 148 encodeURIComponent("." + collapsedClass +
147 "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb azdummy) !important;}"), null, null); 149 "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb azdummy) !important;}"), null, null);
148 Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi ce.USER_SHEET); 150 Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi ce.USER_SHEET);
149 onShutdown.add(function() 151 onShutdown.add(() =>
150 { 152 {
151 Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService. USER_SHEET); 153 Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService. USER_SHEET);
152 }); 154 });
153 }, 155 },
154 156
155 /** 157 /**
156 * Checks whether a node should be blocked, hides it if necessary 158 * Checks whether a node should be blocked, hides it if necessary
157 * @param wnd {nsIDOMWindow} 159 * @param wnd {nsIDOMWindow}
158 * @param node {nsIDOMElement} 160 * @param node {nsIDOMElement}
159 * @param contentType {String} 161 * @param contentType {String}
160 * @param location {nsIURI} 162 * @param location {nsIURI}
161 * @param collapse {Boolean} true to force hiding of the node 163 * @param collapse {Boolean} true to force hiding of the node
162 * @return {Boolean} false if the node should be blocked 164 * @return {Boolean} false if the node should be blocked
163 */ 165 */
164 processNode: function(wnd, node, contentType, location, collapse) 166 processNode: function(wnd, node, contentType, location, collapse)
165 { 167 {
166 let topWnd = wnd.top; 168 let topWnd = wnd.top;
167 if (!topWnd || !topWnd.location || !topWnd.location.href) 169 if (!topWnd || !topWnd.location || !topWnd.location.href)
168 return true; 170 return true;
169 171
170 let originWindow = Utils.getOriginWindow(wnd); 172 let originWindow = Utils.getOriginWindow(wnd);
171 let wndLocation = originWindow.location.href; 173 let wndLocation = originWindow.location.href;
172 let docDomain = getHostname(wndLocation); 174 let docDomain = getHostname(wndLocation);
173 let match = null; 175 let match = null;
174 let [sitekey, sitekeyWnd] = getSitekey(wnd); 176 let [sitekey, sitekeyWnd] = getSitekey(wnd);
175 let nogeneric = false; 177 let nogeneric = false;
176 178
177 function cleanWindowLocation(wnd) 179 function cleanWindowLocation(wnd)
Thomas Greiner 2015/11/10 18:22:33 This function doesn't need to be inside `processNo
Wladimir Palant 2015/11/10 19:13:15 Given that this is a temporary solution I'd rather
178 { 180 {
179 let url = getWindowLocation(wnd); 181 let url = getWindowLocation(wnd);
180 let index = url.indexOf("#"); 182 let index = url.indexOf("#");
181 if (index >= 0) 183 if (index >= 0)
182 url = url.substring(0, index); 184 url = url.substring(0, index);
183 185
184 return url; 186 return url;
185 } 187 }
186 188
187 if (!match && Prefs.enabled) 189 if (!match && Prefs.enabled)
188 { 190 {
189 let testWnd = wnd; 191 let testWnd = wnd;
190 let testSitekey = sitekey; 192 let testSitekey = sitekey;
191 let testSitekeyWnd = sitekeyWnd; 193 let testSitekeyWnd = sitekeyWnd;
192 let parentWndLocation = cleanWindowLocation(testWnd); 194 let parentWndLocation = cleanWindowLocation(testWnd);
193 while (true) 195 while (true)
194 { 196 {
195 let testWndLocation = parentWndLocation; 197 let testWndLocation = parentWndLocation;
196 parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : clean WindowLocation(testWnd.parent)); 198 parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : clean WindowLocation(testWnd.parent));
197 let parentDocDomain = getHostname(parentWndLocation); 199 let parentDocDomain = getHostname(parentWndLocation);
198 200
199 let typeMap = RegExpFilter.typeMap.DOCUMENT; 201 let typeMap = RegExpFilter.typeMap.DOCUMENT;
200 if (contentType == Policy.type.ELEMHIDE) 202 if (contentType == Policy.type.ELEMHIDE)
201 typeMap = typeMap | RegExpFilter.typeMap.ELEMHIDE; 203 typeMap = typeMap | RegExpFilter.typeMap.ELEMHIDE;
202 let whitelistMatch = defaultMatcher.matchesAny(testWndLocation, typeMap, parentDocDomain, false, sitekey); 204 let whitelistMatch = defaultMatcher.matchesAny(testWndLocation, typeMap, parentDocDomain, false, testSitekey);
Thomas Greiner 2015/11/10 18:22:34 `sitekey` is the bottom window's sitekey so should
Wladimir Palant 2015/11/10 19:13:15 Yes, this should really be testSitekey - this was
Wladimir Palant 2015/11/11 07:54:20 Done.
203 if (whitelistMatch instanceof WhitelistFilter) 205 if (whitelistMatch instanceof WhitelistFilter)
204 { 206 {
205 FilterStorage.increaseHitCount(whitelistMatch, wnd); 207 FilterStorage.increaseHitCount(whitelistMatch, wnd);
206 RequestNotifier.addNodeData(testWnd.document, topWnd, 208 RequestNotifier.addNodeData(testWnd.document, topWnd,
207 (whitelistMatch.contentType & RegExpFilter.typeMap.DOCUMENT) ? Polic y.type.DOCUMENT : Policy.type.ELEMHIDE, 209 (whitelistMatch.contentType & RegExpFilter.typeMap.DOCUMENT) ? Polic y.type.DOCUMENT : Policy.type.ELEMHIDE,
208 parentDocDomain, false, testWndLocation, whitelistMatch); 210 parentDocDomain, false, testWndLocation, whitelistMatch);
209 return true; 211 return true;
210 } 212 }
211 213
212 let genericType = (contentType == Policy.type.ELEMHIDE ? 214 let genericType = (contentType == Policy.type.ELEMHIDE ?
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
292 return !match || match instanceof WhitelistFilter; 294 return !match || match instanceof WhitelistFilter;
293 }, 295 },
294 296
295 /** 297 /**
296 * Checks whether the location's scheme is blockable. 298 * Checks whether the location's scheme is blockable.
297 * @param location {nsIURI} 299 * @param location {nsIURI}
298 * @return {Boolean} 300 * @return {Boolean}
299 */ 301 */
300 isBlockableScheme: function(location) 302 isBlockableScheme: function(location)
301 { 303 {
302 return !(location.scheme in Policy.whitelistSchemes); 304 return !this.whitelistSchemes.has(location.scheme);
303 }, 305 },
304 306
305 /** 307 /**
306 * Checks whether a page is whitelisted. 308 * Checks whether a page is whitelisted.
307 * @param {String} url 309 * @param {String} url
308 * @param {String} [parentUrl] location of the parent page 310 * @param {String} [parentUrl] location of the parent page
309 * @param {String} [sitekey] public key provided on the page 311 * @param {String} [sitekey] public key provided on the page
310 * @return {Filter} filter that matched the URL or null if not whitelisted 312 * @return {Filter} filter that matched the URL or null if not whitelisted
311 */ 313 */
312 isWhitelisted: function(url, parentUrl, sitekey) 314 isWhitelisted: function(url, parentUrl, sitekey)
313 { 315 {
314 if (!url) 316 if (!url)
315 return null; 317 return null;
316 318
317 // Do not apply exception rules to schemes on our whitelistschemes list. 319 // Do not apply exception rules to schemes on our whitelistschemes list.
318 let match = /^([\w\-]+):/.exec(url); 320 let match = /^([\w\-]+):/.exec(url);
319 if (match && match[1] in Policy.whitelistSchemes) 321 if (match && this.whitelistSchemes.has(match[1]))
320 return null; 322 return null;
321 323
322 if (!parentUrl) 324 if (!parentUrl)
323 parentUrl = url; 325 parentUrl = url;
324 326
325 // Ignore fragment identifier 327 // Ignore fragment identifier
326 let index = url.indexOf("#"); 328 let index = url.indexOf("#");
327 if (index >= 0) 329 if (index >= 0)
328 url = url.substring(0, index); 330 url = url.substring(0, index);
329 331
330 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g etHostname(parentUrl), false, sitekey); 332 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g etHostname(parentUrl), false, sitekey);
331 return (result instanceof WhitelistFilter ? result : null); 333 return (result instanceof WhitelistFilter ? result : null);
332 }, 334 },
333 335
334 /** 336 /**
335 * Checks whether the page loaded in a window is whitelisted for indication in the UI. 337 * Checks whether the page loaded in a window is whitelisted for indication in the UI.
336 * @param wnd {nsIDOMWindow} 338 * @param wnd {nsIDOMWindow}
337 * @return {Filter} matching exception rule or null if not whitelisted 339 * @return {Filter} matching exception rule or null if not whitelisted
338 */ 340 */
339 isWindowWhitelisted: function(wnd) 341 isWindowWhitelisted: function(wnd)
340 { 342 {
341 return Policy.isWhitelisted(getWindowLocation(wnd)); 343 return this.isWhitelisted(getWindowLocation(wnd));
342 }, 344 },
343 345
344 /** 346 /**
345 * Asynchronously re-checks filters for given nodes. 347 * Asynchronously re-checks filters for given nodes.
346 * @param {Node[]} nodes 348 * @param {Node[]} nodes
347 * @param {RequestEntry} entry 349 * @param {RequestEntry} entry
348 */ 350 */
349 refilterNodes: function(nodes, entry) 351 refilterNodes: function(nodes, entry)
350 { 352 {
351 // Ignore nodes that have been blocked already 353 // Ignore nodes that have been blocked already
(...skipping 24 matching lines...) Expand all
376 { 378 {
377 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); 379 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
378 registrar.registerFactory(this.classID, this.classDescription, this.contract ID, this); 380 registrar.registerFactory(this.classID, this.classDescription, this.contract ID, this);
379 381
380 let catMan = Utils.categoryManager; 382 let catMan = Utils.categoryManager;
381 for (let category of this.xpcom_categories) 383 for (let category of this.xpcom_categories)
382 catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true); 384 catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true);
383 385
384 Services.obs.addObserver(this, "content-document-global-created", true); 386 Services.obs.addObserver(this, "content-document-global-created", true);
385 387
386 onShutdown.add(function() 388 onShutdown.add(() =>
387 { 389 {
388 Services.obs.removeObserver(this, "content-document-global-created"); 390 Services.obs.removeObserver(this, "content-document-global-created");
389 391
390 for (let category of this.xpcom_categories) 392 for (let category of this.xpcom_categories)
391 catMan.deleteCategoryEntry(category, this.contractID, false); 393 catMan.deleteCategoryEntry(category, this.contractID, false);
392 394
393 registrar.unregisterFactory(this.classID, this); 395 registrar.unregisterFactory(this.classID, this);
394 }.bind(this)); 396 });
395 }, 397 },
396 398
397 // 399 //
398 // nsISupports interface implementation 400 // nsISupports interface implementation
399 // 401 //
400 402
401 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, 403 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
402 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), 404 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]),
403 405
404 // 406 //
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
452 let uri = additional || Utils.makeURI(subject.location.href); 454 let uri = additional || Utils.makeURI(subject.location.href);
453 if (!Policy.processNode(subject.opener, subject.opener.document, Policy. type.POPUP, uri, false)) 455 if (!Policy.processNode(subject.opener, subject.opener.document, Policy. type.POPUP, uri, false))
454 { 456 {
455 subject.stop(); 457 subject.stop();
456 Utils.runAsync(() => subject.close()); 458 Utils.runAsync(() => subject.close());
457 } 459 }
458 else if (uri.spec == "about:blank") 460 else if (uri.spec == "about:blank")
459 { 461 {
460 // An about:blank pop-up most likely means that a load will be 462 // An about:blank pop-up most likely means that a load will be
461 // initiated asynchronously. Wait for that. 463 // initiated asynchronously. Wait for that.
462 Utils.runAsync(function() 464 Utils.runAsync(() =>
463 { 465 {
464 let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor) 466 let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor)
465 .getInterface(Ci.nsIDocShell) 467 .getInterface(Ci.nsIDocShell)
466 .QueryInterface(Ci.nsIDocumentLoader) 468 .QueryInterface(Ci.nsIDocumentLoader)
467 .documentChannel; 469 .documentChannel;
468 if (channel) 470 if (channel)
469 this.observe(subject, topic, data, channel.URI); 471 this.observe(subject, topic, data, channel.URI);
470 }); 472 });
471 } 473 }
472 break; 474 break;
473 } 475 }
474 } 476 }
475 }, 477 },
476 478
477 // 479 //
478 // nsIChannelEventSink interface implementation 480 // nsIChannelEventSink interface implementation
479 // 481 //
480 482
481 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) 483 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
482 { 484 {
483 let result = Cr.NS_OK; 485 let result = Cr.NS_OK;
484 try 486 try
485 { 487 {
486 // nsILoadInfo.contentPolicyType was introduced in Gecko 35, then 488 // nsILoadInfo.contentPolicyType was introduced in Gecko 35, then
487 // renamed to nsILoadInfo.externalContentPolicyType in Gecko 44. 489 // renamed to nsILoadInfo.externalContentPolicyType in Gecko 44.
488 let loadInfo = oldChannel.loadInfo; 490 let loadInfo = oldChannel.loadInfo;
489 let contentType = loadInfo.externalContentPolicyType || loadInfo.contentPo licyType; 491 let contentType = ("externalContentPolicyType" in loadInfo ?
492 loadInfo.externalContentPolicyType : loadInfo.contentPolicyType);
490 if (!contentType) 493 if (!contentType)
491 return; 494 return;
492 495
493 let wnd = Utils.getRequestWindow(newChannel); 496 let wnd = Utils.getRequestWindow(newChannel);
494 if (!wnd) 497 if (!wnd)
495 return; 498 return;
496 499
497 if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) 500 if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT)
498 { 501 {
499 if (wnd.history.length <= 1 && wnd.opener) 502 if (wnd.history.length <= 1 && wnd.opener)
500 { 503 {
501 // Special treatment for pop-up windows. Note that we might not have 504 // Special treatment for pop-up windows - this will close the window
505 // rather than preventing the redirect. Note that we might not have
502 // seen the original channel yet because the redirect happened before 506 // seen the original channel yet because the redirect happened before
503 // the async code in observe() had a chance to run. 507 // the async code in observe() had a chance to run.
504 this.observe(wnd, "content-document-global-created", null, oldChannel. URI); 508 this.observe(wnd, "content-document-global-created", null, oldChannel. URI);
505 this.observe(wnd, "content-document-global-created", null, newChannel. URI); 509 this.observe(wnd, "content-document-global-created", null, newChannel. URI);
506 } 510 }
507 return; 511 return;
508 } 512 }
509 513
510 if (!Policy.processNode(wnd, wnd.document, contentType, newChannel.URI, fa lse)) 514 if (!Policy.processNode(wnd, wnd.document, contentType, newChannel.URI, fa lse))
511 result = Cr.NS_BINDING_ABORTED; 515 result = Cr.NS_BINDING_ABORTED;
(...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after
719 if (!wnd || wnd.closed) 723 if (!wnd || wnd.closed)
720 return; 724 return;
721 725
722 if (entry.type == Policy.type.OBJECT) 726 if (entry.type == Policy.type.OBJECT)
723 { 727 {
724 node.removeEventListener("mouseover", objectMouseEventHander, true); 728 node.removeEventListener("mouseover", objectMouseEventHander, true);
725 node.removeEventListener("mouseout", objectMouseEventHander, true); 729 node.removeEventListener("mouseout", objectMouseEventHander, true);
726 } 730 }
727 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true) ; 731 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true) ;
728 } 732 }
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld