You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

265 lines
8.1 KiB

/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Content policy to be loaded in the content process for a multi-process setup (currently only Fennec)
*/
var EXPORTED_SYMBOLS = ["PolicyRemote"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://@ADDON_CHROME_NAME@/modules/Utils.jsm");
/**
* nsIContentPolicy and nsIChannelEventSink implementation
* @class
*/
var PolicyRemote =
{
classDescription: "Adblock Plus content policy",
classID: Components.ID("094560a0-4fed-11e0-b8af-0800200c9a66"),
contractID: "@adblockplus.org/abp/policy-remote;1",
xpcom_categories: ["content-policy", "net-channel-event-sinks"],
cache: new Cache(512),
startup: function()
{
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
try
{
registrar.registerFactory(PolicyRemote.classID, PolicyRemote.classDescription, PolicyRemote.contractID, PolicyRemote);
}
catch (e)
{
// Don't stop on errors - the factory might already be registered
Cu.reportError(e);
}
let catMan = Utils.categoryManager;
for each (let category in PolicyRemote.xpcom_categories)
catMan.addCategoryEntry(category, PolicyRemote.classDescription, PolicyRemote.contractID, false, true);
Services.obs.addObserver(PolicyRemote, "http-on-modify-request", true);
Services.obs.addObserver(PolicyRemote, "content-document-global-created", true);
// Generate class identifier used to collapse node and register corresponding
// stylesheet.
let offset = "a".charCodeAt(0);
Utils.collapsedClass = "";
for (let i = 0; i < 20; i++)
Utils.collapsedClass += String.fromCharCode(offset + Math.random() * 26);
let collapseStyle = Utils.makeURI("data:text/css," +
encodeURIComponent("." + Utils.collapsedClass +
"{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarbazdummy) !important;}"));
Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetService.USER_SHEET);
// Get notified if we need to invalidate our matching cache
Utils.childMessageManager.addMessageListener("AdblockPlus:Matcher:clearCache", function(message)
{
PolicyRemote.cache.clear();
});
},
//
// nsISupports interface implementation
//
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]),
//
// nsIContentPolicy interface implementation
//
shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra)
{
// Ignore requests without context and top-level documents
if (!node || contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT)
return Ci.nsIContentPolicy.ACCEPT;
let wnd = Utils.getWindow(node);
if (!wnd)
return Ci.nsIContentPolicy.ACCEPT;
wnd = Utils.getOriginWindow(wnd);
let locations = [];
let testWnd = wnd;
while (true)
{
locations.push(testWnd.location.href);
if (testWnd.parent == testWnd)
break;
else
testWnd = testWnd.parent;
}
let key = contentType + " " + contentLocation.spec + " " + locations.join(" ");
if (!(key in this.cache.data))
{
this.cache.add(key, Utils.childMessageManager.sendSyncMessage("AdblockPlus:Policy:shouldLoad", {
contentType: contentType,
contentLocation: contentLocation.spec,
locations: locations})[0]);
}
let result = this.cache.data[key];
if (result.value == Ci.nsIContentPolicy.ACCEPT)
{
// We didn't block this request so we will probably see it again in
// http-on-modify-request. Keep it so that we can associate it with the
// channel there - will be needed in case of redirect.
PolicyRemote.previousRequest = [Utils.unwrapURL(contentLocation), contentType];
}
else if (result.postProcess)
Utils.schedulePostProcess(node);
return result.value;
},
shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra)
{
return Ci.nsIContentPolicy.ACCEPT;
},
//
// nsIObserver interface implementation
//
observe: function(subject, topic, data, additional)
{
switch (topic)
{
case "content-document-global-created":
{
if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener)
return;
let uri = additional || Utils.makeURI(subject.location.href);
if (PolicyRemote.shouldLoad(0xFFFE /*Policy.type.POPUP*/, uri, null, subject.opener.document, null, null) != Ci.nsIContentPolicy.ACCEPT)
{
subject.stop();
Utils.runAsync(subject.close, subject);
}
else if (uri.spec == "about:blank")
{
// An about:blank pop-up most likely means that a load will be
// initiated synchronously. Set a flag for our "http-on-modify-request"
// handler.
PolicyRemote.expectingPopupLoad = true;
Utils.runAsync(function()
{
PolicyRemote.expectingPopupLoad = false;
});
}
break;
}
case "http-on-modify-request":
{
if (!(subject instanceof Ci.nsIHttpChannel))
return;
// TODO: Do-not-track header
if (PolicyRemote.previousRequest && subject.URI == PolicyRemote.previousRequest[0] &&
subject instanceof Ci.nsIWritablePropertyBag)
{
// We just handled a content policy call for this request - associate
// the data with the channel so that we can find it in case of a redirect.
subject.setProperty("abpRequestType", PolicyRemote.previousRequest[1]);
PolicyRemote.previousRequest = null;
}
if (PolicyRemote.expectingPopupLoad)
{
let wnd = Utils.getRequestWindow(subject);
if (wnd && wnd.opener && wnd.location.href == "about:blank")
PolicyRemote.observe(wnd, "content-document-global-created", null, subject.URI);
}
break;
}
}
},
//
// nsIChannelEventSink interface implementation
//
onChannelRedirect: function(oldChannel, newChannel, flags)
{
try
{
// Try to retrieve previously stored request data from the channel
let contentType;
if (oldChannel instanceof Ci.nsIWritablePropertyBag)
{
try
{
contentType = oldChannel.getProperty("abpRequestType");
}
catch(e)
{
// No data attached, ignore this redirect
return;
}
}
let newLocation = null;
try
{
newLocation = newChannel.URI;
} catch(e2) {}
if (!newLocation)
return;
let wnd = Utils.getRequestWindow(newChannel);
if (!wnd)
return;
// HACK: NS_BINDING_ABORTED would be proper error code to throw but this will show up in error console (bug 287107)
if (PolicyRemote.shouldLoad(contentType, newLocation, null, wnd.document) != Ci.nsIContentPolicy.ACCEPT)
throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
else
return;
}
catch (e if (e != Cr.NS_BASE_STREAM_WOULD_BLOCK))
{
// We shouldn't throw exceptions here - this will prevent the redirect.
Cu.reportError(e);
}
},
asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
{
this.onChannelRedirect(oldChannel, newChannel, flags);
// If onChannelRedirect didn't throw an exception indicate success
callback.onRedirectVerifyCallback(Cr.NS_OK);
},
//
// nsIFactory interface implementation
//
createInstance: function(outer, iid)
{
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
}
};
PolicyRemote.startup();