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

Delta Between Two Patch Sets: lib/contentPolicy.js

Issue 29329404: Issue 443 - Fix blocking pop-ups after redirect (Closed)
Left Patch Set: Created Oct. 26, 2015, 8:28 p.m.
Right Patch Set: Improved comment Created Nov. 5, 2015, 3:51 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 | « 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}
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after
298 return !match || match instanceof WhitelistFilter; 300 return !match || match instanceof WhitelistFilter;
299 }, 301 },
300 302
301 /** 303 /**
302 * Checks whether the location's scheme is blockable. 304 * Checks whether the location's scheme is blockable.
303 * @param location {nsIURI} 305 * @param location {nsIURI}
304 * @return {Boolean} 306 * @return {Boolean}
305 */ 307 */
306 isBlockableScheme: function(location) 308 isBlockableScheme: function(location)
307 { 309 {
308 return !(location.scheme in Policy.whitelistSchemes); 310 return !this.whitelistSchemes.has(location.scheme);
309 }, 311 },
310 312
311 /** 313 /**
312 * Checks whether a page is whitelisted. 314 * Checks whether a page is whitelisted.
313 * @param {String} url 315 * @param {String} url
314 * @param {String} [parentUrl] location of the parent page 316 * @param {String} [parentUrl] location of the parent page
315 * @param {String} [sitekey] public key provided on the page 317 * @param {String} [sitekey] public key provided on the page
316 * @return {Filter} filter that matched the URL or null if not whitelisted 318 * @return {Filter} filter that matched the URL or null if not whitelisted
317 */ 319 */
318 isWhitelisted: function(url, parentUrl, sitekey) 320 isWhitelisted: function(url, parentUrl, sitekey)
319 { 321 {
320 if (!url) 322 if (!url)
321 return null; 323 return null;
322 324
323 // Do not apply exception rules to schemes on our whitelistschemes list. 325 // Do not apply exception rules to schemes on our whitelistschemes list.
324 let match = /^([\w\-]+):/.exec(url); 326 let match = /^([\w\-]+):/.exec(url);
325 if (match && match[1] in Policy.whitelistSchemes) 327 if (match && this.whitelistSchemes.has(match[1]))
326 return null; 328 return null;
327 329
328 if (!parentUrl) 330 if (!parentUrl)
329 parentUrl = url; 331 parentUrl = url;
330 332
331 // Ignore fragment identifier 333 // Ignore fragment identifier
332 let index = url.indexOf("#"); 334 let index = url.indexOf("#");
333 if (index >= 0) 335 if (index >= 0)
334 url = url.substring(0, index); 336 url = url.substring(0, index);
335 337
336 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g etHostname(parentUrl), false, sitekey); 338 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g etHostname(parentUrl), false, sitekey);
337 return (result instanceof WhitelistFilter ? result : null); 339 return (result instanceof WhitelistFilter ? result : null);
338 }, 340 },
339 341
340 /** 342 /**
341 * Checks whether the page loaded in a window is whitelisted for indication in the UI. 343 * Checks whether the page loaded in a window is whitelisted for indication in the UI.
342 * @param wnd {nsIDOMWindow} 344 * @param wnd {nsIDOMWindow}
343 * @return {Filter} matching exception rule or null if not whitelisted 345 * @return {Filter} matching exception rule or null if not whitelisted
344 */ 346 */
345 isWindowWhitelisted: function(wnd) 347 isWindowWhitelisted: function(wnd)
346 { 348 {
347 return Policy.isWhitelisted(getWindowLocation(wnd)); 349 return this.isWhitelisted(getWindowLocation(wnd));
348 }, 350 },
349 351
350 /** 352 /**
351 * Asynchronously re-checks filters for given nodes. 353 * Asynchronously re-checks filters for given nodes.
352 * @param {Node[]} nodes 354 * @param {Node[]} nodes
353 * @param {RequestEntry} entry 355 * @param {RequestEntry} entry
354 */ 356 */
355 refilterNodes: function(nodes, entry) 357 refilterNodes: function(nodes, entry)
356 { 358 {
357 // Ignore nodes that have been blocked already 359 // Ignore nodes that have been blocked already
(...skipping 22 matching lines...) Expand all
380 */ 382 */
381 init: function() 383 init: function()
382 { 384 {
383 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); 385 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
384 registrar.registerFactory(this.classID, this.classDescription, this.contract ID, this); 386 registrar.registerFactory(this.classID, this.classDescription, this.contract ID, this);
385 387
386 let catMan = Utils.categoryManager; 388 let catMan = Utils.categoryManager;
387 for (let category of this.xpcom_categories) 389 for (let category of this.xpcom_categories)
388 catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true); 390 catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true);
389 391
390 // http-on-opening-request is new in Gecko 18, http-on-modify-request can
391 // be used in earlier releases.
392 let httpTopic = "http-on-opening-request";
393 if (Services.vc.compare(Utils.platformVersion, "18.0") < 0)
394 httpTopic = "http-on-modify-request";
395
396 Services.obs.addObserver(this, httpTopic, true);
397 Services.obs.addObserver(this, "content-document-global-created", true); 392 Services.obs.addObserver(this, "content-document-global-created", true);
398 393
399 onShutdown.add(function() 394 onShutdown.add(() =>
400 { 395 {
401 // Our category observers should be removed before changing category
402 // memberships, just in case.
403 Services.obs.removeObserver(this, httpTopic);
404 Services.obs.removeObserver(this, "content-document-global-created"); 396 Services.obs.removeObserver(this, "content-document-global-created");
405 397
406 for (let category of this.xpcom_categories) 398 for (let category of this.xpcom_categories)
407 catMan.deleteCategoryEntry(category, this.contractID, false); 399 catMan.deleteCategoryEntry(category, this.contractID, false);
408 400
409 registrar.unregisterFactory(this.classID, this); 401 registrar.unregisterFactory(this.classID, this);
410 402 });
411 this.previousRequest = null;
412 }.bind(this));
413 }, 403 },
414 404
415 // 405 //
416 // nsISupports interface implementation 406 // nsISupports interface implementation
417 // 407 //
418 408
419 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, 409 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
420 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), 410 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]),
421 411
422 // 412 //
(...skipping 17 matching lines...) Expand all
440 // Ignore whitelisted schemes 430 // Ignore whitelisted schemes
441 let location = Utils.unwrapURL(contentLocation); 431 let location = Utils.unwrapURL(contentLocation);
442 if (!Policy.isBlockableScheme(location)) 432 if (!Policy.isBlockableScheme(location))
443 return Ci.nsIContentPolicy.ACCEPT; 433 return Ci.nsIContentPolicy.ACCEPT;
444 434
445 // Interpret unknown types as "other" 435 // Interpret unknown types as "other"
446 if (!(contentType in Policy.typeDescr)) 436 if (!(contentType in Policy.typeDescr))
447 contentType = Policy.type.OTHER; 437 contentType = Policy.type.OTHER;
448 438
449 let result = Policy.processNode(wnd, node, contentType, location, false); 439 let result = Policy.processNode(wnd, node, contentType, location, false);
450 if (result)
451 {
452 // We didn't block this request so we will probably see it again in
453 // http-on-opening-request. Keep it so that we can associate it with the
454 // channel there - will be needed in case of redirect.
455 this.previousRequest = [location, contentType];
456 }
457 return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQ UEST); 440 return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQ UEST);
458 }, 441 },
459 442
460 shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode , mimeType, extra) 443 shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode , mimeType, extra)
461 { 444 {
462 return Ci.nsIContentPolicy.ACCEPT; 445 return Ci.nsIContentPolicy.ACCEPT;
463 }, 446 },
464 447
465 // 448 //
466 // nsIObserver interface implementation 449 // nsIObserver interface implementation
(...skipping 10 matching lines...) Expand all
477 let uri = additional || Utils.makeURI(subject.location.href); 460 let uri = additional || Utils.makeURI(subject.location.href);
478 if (!Policy.processNode(subject.opener, subject.opener.document, Policy. type.POPUP, uri, false)) 461 if (!Policy.processNode(subject.opener, subject.opener.document, Policy. type.POPUP, uri, false))
479 { 462 {
480 subject.stop(); 463 subject.stop();
481 Utils.runAsync(() => subject.close()); 464 Utils.runAsync(() => subject.close());
482 } 465 }
483 else if (uri.spec == "about:blank") 466 else if (uri.spec == "about:blank")
484 { 467 {
485 // An about:blank pop-up most likely means that a load will be 468 // An about:blank pop-up most likely means that a load will be
486 // initiated asynchronously. Wait for that. 469 // initiated asynchronously. Wait for that.
487 Utils.runAsync(function() 470 Utils.runAsync(() =>
488 { 471 {
489 let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor) 472 let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor)
490 .getInterface(Ci.nsIDocShell) 473 .getInterface(Ci.nsIDocShell)
491 .QueryInterface(Ci.nsIDocumentLoader) 474 .QueryInterface(Ci.nsIDocumentLoader)
492 .documentChannel; 475 .documentChannel;
493 if (channel) 476 if (channel)
494 this.observe(subject, topic, data, channel.URI); 477 this.observe(subject, topic, data, channel.URI);
495 }); 478 });
496 } 479 }
497 break; 480 break;
498 } 481 }
499 case "http-on-opening-request":
500 case "http-on-modify-request":
501 {
502 if (!(subject instanceof Ci.nsIHttpChannel))
503 return;
504
505 if (this.previousRequest && subject.URI == this.previousRequest[0] &&
506 subject instanceof Ci.nsIWritablePropertyBag)
507 {
508 // We just handled a content policy call for this request - associate
509 // the data with the channel so that we can find it in case of a redir ect.
510 subject.setProperty("abpRequestType", this.previousRequest[1]);
511 this.previousRequest = null;
512 }
513 break;
514 }
515 } 482 }
516 }, 483 },
517 484
518 // 485 //
519 // nsIChannelEventSink interface implementation 486 // nsIChannelEventSink interface implementation
520 // 487 //
521 488
522 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) 489 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
523 { 490 {
524 let result = Cr.NS_OK; 491 let result = Cr.NS_OK;
525 try 492 try
526 { 493 {
527 // Try to retrieve previously stored request data from the channel 494 // nsILoadInfo.contentPolicyType was introduced in Gecko 35, then
528 let contentType; 495 // renamed to nsILoadInfo.externalContentPolicyType in Gecko 44.
529 if (oldChannel instanceof Ci.nsIWritablePropertyBag) 496 let loadInfo = oldChannel.loadInfo;
530 { 497 let contentType = ("externalContentPolicyType" in loadInfo ?
531 try 498 loadInfo.externalContentPolicyType : loadInfo.contentPolicyType);
532 { 499 if (!contentType)
533 contentType = oldChannel.getProperty("abpRequestType");
534 }
535 catch(e)
536 {
537 // No data attached, ignore this redirect
538 return;
539 }
540 }
541
542 let newLocation = null;
543 try
544 {
545 newLocation = newChannel.URI;
546 } catch(e2) {}
547 if (!newLocation)
548 return; 500 return;
549 501
550 let wnd = Utils.getRequestWindow(newChannel); 502 let wnd = Utils.getRequestWindow(newChannel);
551 if (!wnd) 503 if (!wnd)
552 return; 504 return;
553 505
554 if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) 506 if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT)
Wladimir Palant 2015/10/26 20:50:26 This relies on the changes from https://codereview
555 { 507 {
556 if (wnd.history.length <= 1 && wnd.opener) 508 if (wnd.history.length <= 1 && wnd.opener)
557 { 509 {
558 // Special treatment for pop-up windows 510 // Special treatment for pop-up windows - this will close the window
559 this.observe(wnd, "content-document-global-created", null, newLocation ); 511 // rather than preventing the redirect. Note that we might not have
512 // seen the original channel yet because the redirect happened before
513 // the async code in observe() had a chance to run.
514 this.observe(wnd, "content-document-global-created", null, oldChannel. URI);
515 this.observe(wnd, "content-document-global-created", null, newChannel. URI);
560 } 516 }
561 return; 517 return;
562 } 518 }
563 519
564 if (!Policy.processNode(wnd, wnd.document, contentType, newLocation, false )) 520 if (!Policy.processNode(wnd, wnd.document, contentType, newChannel.URI, fa lse))
565 result = Cr.NS_BINDING_ABORTED; 521 result = Cr.NS_BINDING_ABORTED;
566 } 522 }
567 catch (e) 523 catch (e)
568 { 524 {
569 // We shouldn't throw exceptions here - this will prevent the redirect. 525 // We shouldn't throw exceptions here - this will prevent the redirect.
570 Cu.reportError(e); 526 Cu.reportError(e);
571 } 527 }
572 finally 528 finally
573 { 529 {
574 callback.onRedirectVerifyCallback(result); 530 callback.onRedirectVerifyCallback(result);
(...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after
773 if (!wnd || wnd.closed) 729 if (!wnd || wnd.closed)
774 return; 730 return;
775 731
776 if (entry.type == Policy.type.OBJECT) 732 if (entry.type == Policy.type.OBJECT)
777 { 733 {
778 node.removeEventListener("mouseover", objectMouseEventHander, true); 734 node.removeEventListener("mouseover", objectMouseEventHander, true);
779 node.removeEventListener("mouseout", objectMouseEventHander, true); 735 node.removeEventListener("mouseout", objectMouseEventHander, true);
780 } 736 }
781 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true) ; 737 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true) ;
782 } 738 }
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