Bug 1007982 - Add shim for adblock about: protocol (r=mconley)

This commit is contained in:
Bill McCloskey 2014-07-18 08:59:00 -07:00
parent 51c7b90b14
commit 95bf0cc7ea
2 changed files with 265 additions and 6 deletions

View File

@ -7,10 +7,17 @@ this.EXPORTED_SYMBOLS = ["RemoteAddonsChild"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "SystemPrincipal",
"@mozilla.org/systemprincipal;1", "nsIPrincipal");
// Similar to Python. Returns dict[key] if it exists. Otherwise,
// sets dict[key] to default_ and returns default_.
function setDefault(dict, key, default_)
@ -112,9 +119,9 @@ let ContentPolicyChild = {
track: function(path, count) {
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
if (count) {
if (count == 1) {
catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
} else {
} else if (count == 0) {
catMan.deleteCategoryEntry("content-policy", this._contractID, false);
}
},
@ -148,6 +155,158 @@ let ContentPolicyChild = {
},
};
// This is a shim channel whose only purpose is to return some string
// data from an about: protocol handler.
function AboutProtocolChannel(data, uri, originalURI, contentType)
{
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
stream.setData(data, data.length);
this._stream = stream;
this.URI = BrowserUtils.makeURI(uri);
this.originalURI = BrowserUtils.makeURI(originalURI);
this.contentType = contentType;
}
AboutProtocolChannel.prototype = {
contentCharset: "utf-8",
contentLength: 0,
owner: SystemPrincipal,
securityInfo: null,
notificationCallbacks: null,
loadFlags: 0,
loadGroup: null,
name: null,
status: Cr.NS_OK,
asyncOpen: function(listener, context) {
let runnable = {
run: () => {
try {
listener.onStartRequest(this, context);
} catch(e) {}
try {
listener.onDataAvailable(this, context, this._stream, 0, this._stream.available());
} catch(e) {}
try {
listener.onStopRequest(this, context, Cr.NS_OK);
} catch(e) {}
}
};
Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
},
open: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
isPending: function() {
return false;
},
cancel: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
suspend: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
resume: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
};
// This shim protocol handler is used when content fetches an about: URL.
function AboutProtocolInstance(contractID)
{
this._contractID = contractID;
this._uriFlags = null;
}
AboutProtocolInstance.prototype = {
createInstance: function(outer, iid) {
if (outer != null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
getURIFlags: function(uri) {
// Cache the result to avoid the extra IPC.
if (this._uriFlags !== undefined) {
return this._uriFlags;
}
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
var rval = cpmm.sendRpcMessage("Addons:AboutProtocol:GetURIFlags", {
uri: uri.spec,
contractID: this._contractID
});
if (rval.length != 1) {
throw Cr.NS_ERROR_FAILURE;
}
this._uriFlags = rval[0];
return this._uriFlags;
},
// We take some shortcuts here. Ideally, we would return a CPOW that
// wraps the add-on's nsIChannel. However, many of the methods
// related to nsIChannel are marked [noscript], so they're not
// available to CPOWs. Consequently, the parent simply reads all the
// data out of the add-on's channel and returns that as a string. We
// create a new AboutProtocolChannel whose only purpose is to return
// the string data via an nsIStringInputStream.
newChannel: function(uri) {
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
var rval = cpmm.sendRpcMessage("Addons:AboutProtocol:NewChannel", {
uri: uri.spec,
contractID: this._contractID
});
if (rval.length != 1) {
throw Cr.NS_ERROR_FAILURE;
}
let {data, uri, originalURI, contentType} = rval[0];
return new AboutProtocolChannel(data, uri, originalURI, contentType);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
};
let AboutProtocolChild = {
_classDescription: "Addon shim about: protocol handler",
_classID: Components.ID("8d56a310-0c80-11e4-9191-0800200c9a66"),
init: function() {
this._instances = {};
NotificationTracker.watch("about-protocol", (path, count) => this.track(path, count));
},
track: function(path, count) {
let contractID = path[1];
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
if (count == 1) {
let instance = new AboutProtocolInstance(contractID);
this._instances[contractID] = instance;
registrar.registerFactory(this._classID, this._classDescription, contractID, instance);
} else if (count == 0) {
delete this._instances[contractID];
registerFactory.unregisterFactory(this._classID, this);
}
},
};
// This code registers observers in the child whenever an add-on in
// the parent asks for notifications on the given topic.
let ObserverChild = {
@ -157,9 +316,9 @@ let ObserverChild = {
track: function(path, count) {
let topic = path[1];
if (count) {
if (count == 1) {
Services.obs.addObserver(this, topic, false);
} else {
} else if (count == 0) {
Services.obs.removeObserver(this, topic);
}
},
@ -188,9 +347,9 @@ EventTargetChild.prototype = {
track: function(path, count) {
let eventType = path[1];
let useCapture = path[2];
if (count) {
if (count == 1) {
this._childGlobal.addEventListener(eventType, this, useCapture, true);
} else {
} else if (count == 0) {
this._childGlobal.removeEventListener(eventType, this, useCapture);
}
},
@ -253,6 +412,7 @@ let RemoteAddonsChild = {
makeReady: function() {
NotificationTracker.init();
ContentPolicyChild.init();
AboutProtocolChild.init();
ObserverChild.init();
},

View File

@ -11,6 +11,11 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import('resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
// Similar to Python. Returns dict[key] if it exists. Otherwise,
// sets dict[key] to default_ and returns default_.
function setDefault(dict, key, default_)
@ -172,6 +177,99 @@ CategoryManagerInterposition.methods.deleteCategoryEntry =
target.deleteCategoryEntry(category, entry, persist);
};
// This shim handles the case where an add-on registers an about:
// protocol handler in the parent and we want the child to be able to
// use it. This code is pretty specific to Adblock's usage.
let AboutProtocolParent = {
init: function() {
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
ppmm.addMessageListener("Addons:AboutProtocol:GetURIFlags", this);
ppmm.addMessageListener("Addons:AboutProtocol:NewChannel", this);
this._protocols = [];
},
registerFactory: function(class_, className, contractID, factory) {
this._protocols.push({contractID: contractID, factory: factory});
NotificationTracker.add(["about-protocol", contractID]);
},
unregisterFactory: function(class_, factory) {
for (let i = 0; i < this._protocols.length; i++) {
if (this._protocols[i].factory == factory) {
NotificationTracker.remove(["about-protocol", this._protocols[i].contractID]);
this._protocols.splice(i, 1);
break;
}
}
},
receiveMessage: function (msg) {
switch (msg.name) {
case "Addons:AboutProtocol:GetURIFlags":
return this.getURIFlags(msg);
case "Addons:AboutProtocol:NewChannel":
return this.newChannel(msg);
break;
}
},
getURIFlags: function(msg) {
let uri = BrowserUtils.makeURI(msg.data.uri);
let contractID = msg.data.contractID;
let module = Cc[contractID].getService(Ci.nsIAboutModule);
try {
return module.getURIFlags(uri);
} catch (e) {
Cu.reportError(e);
}
},
// We take some shortcuts here. Ideally, we would return a CPOW that
// wraps the add-on's nsIChannel. However, many of the methods
// related to nsIChannel are marked [noscript], so they're not
// available to CPOWs. Consequently, we immediately read all the
// data out of the channel here and pass it to the child. The child
// then returns a shim channel that wraps an nsIStringInputStream
// for the string we read.
newChannel: function(msg) {
let uri = BrowserUtils.makeURI(msg.data.uri);
let contractID = msg.data.contractID;
let module = Cc[contractID].getService(Ci.nsIAboutModule);
try {
let channel = module.newChannel(uri);
let stream = channel.open();
let data = NetUtil.readInputStreamToString(stream, stream.available(), {});
return {
data: data,
uri: channel.URI.spec,
originalURI: channel.originalURI.spec,
contentType: channel.contentType
};
} catch (e) {
Cu.reportError(e);
}
},
};
AboutProtocolParent.init();
let ComponentRegistrarInterposition = new Interposition();
ComponentRegistrarInterposition.methods.registerFactory =
function(addon, target, class_, className, contractID, factory) {
if (contractID.startsWith("@mozilla.org/network/protocol/about;1?")) {
AboutProtocolParent.registerFactory(class_, className, contractID, factory);
}
target.registerFactory(class_, className, contractID, factory);
};
ComponentRegistrarInterposition.methods.unregisterFactory =
function(addon, target, class_, factory) {
AboutProtocolParent.tryUnregisterFactory(class_, factory);
target.unregisterFactory(class_, factory);
};
// This object manages add-on observers that might fire in the child
// process. Rather than managing the observers itself, it uses the
// parent's observer service. When an add-on listens on topic T,
@ -549,6 +647,7 @@ let RemoteAddonsParent = {
}
register(Ci.nsICategoryManager, CategoryManagerInterposition);
register(Ci.nsIComponentRegistrar, ComponentRegistrarInterposition);
register(Ci.nsIObserverService, ObserverInterposition);
register(Ci.nsIXPCComponents_Utils, ComponentsUtilsInterposition);