Alrighty lets give a more sensible example of a custom protocol handler.
I decided to implement a ddg:
protocol handler, that once registered can be used to type ddg:some search terms
into the address bar (among other things) and it will load the DuckDuckGo search page for "some search terms".
The component
One needs to implement the nsIProtocolHandler
interface.
What this example component does is "redirect" to DuckDuckGo (well, not really redirect, but it returns a channel for duckduckgo.com). See comments inline.
var {classes: Cc,
interfaces: Ci,
manager: Cm,
results: Cr,
Constructor: CC
} = Components;
Cm.QueryInterface(Ci.nsIComponentRegistrar);
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const SCHEME = "ddg";
const DDG_URI = Services.io.newURI("https://duckduckgo.com/?q=%s", null, null);
const nsIURI = CC("@mozilla.org/network/simple-uri;1", "nsIURI");
function DuckDuckGoProtocolHandler() {}
DuckDuckGoProtocolHandler.prototype = Object.freeze({
classDescription: "DuckDuckGo Protocol Handler",
contractID: "@mozilla.org/network/protocol;1?name=" + SCHEME,
classID: Components.ID('{858ea860-129a-11e4-9191-0800200c9a66}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
// nsIProtocolHandler
scheme: SCHEME,
defaultPort: -1, // No default port.
// nsIProtocolHandler
allowPort: function(port, scheme) {
// This protocol handler does not support ports.
return false;
},
// nsIProtocolHandler
// Our protocol handler does not support authentication,
// but it is OK to be loaded from any web-page, not just privileged pages""
protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,
// nsIProtocolHandler
newURI: function(aSpec, aOriginCharset, aBaseURI) {
// Nothing special here, actually. We were asked to create a new URI.
// If there is a base-URI, this means that the browser tries to resolve
// a dependent resource (an image, script) or the user clicked on a relative link.
// In this case we cannot really return another "ddg" URI, but need to return
// the proper https URI.
if (aBaseURI && aBaseURI.scheme == SCHEME) {
return Services.io.newURI(aSpec, aOriginCharset, DDG_URI);
}
// We don't care about the charset, so just ignore that
// (we support what nsIURI supports).
let rv = new nsIURI();
rv.spec = aSpec;
return rv;
},
// nsIProtocolHandler
newChannel: function(aURI) {
// We were asked to open a new channel.
// We could implement an entirely custom channel that supports
// (most of) nsIChannel. But that is tremendous work and outside
// of the scope of this basic example (which is about protocol handlers and
// not channels).
// Or we can just return any other channel we can create.
// Since we're going to implement the "ddg:" protocol, lets just open a
// regular https channel to duckduckgo.com, use the URI as the search term
// and return that channel.
let spec = DDG_URI.spec.replace("%s", aURI.path);
let channel = Services.io.newChannel(spec, aURI.originCharset, null);
// Setting .originalURI will not only let other code know where this
// originally came from, but the UI will actually show that .originalURI.
channel.originalURI = aURI;
return channel;
}
});
Component registration in chrome.manifest
We need to implement NSGetFactory
if our component is a JavaScript component
registered via chrome.manifest. Luckily, XPCOMUtils.jsm
has a helper for that.
var NSGetFactory =
XPCOMUtils.generateNSGetFactory([DuckDuckGoProtocolHandler]);
Registration in bootstrapped add-ons (and Scratchpad)
In bootstrapped/restartless add-ons (incl. SDK add-ons) and Scratchpad, one will need to register the component manually, as chrome.manifest
registration is not available.
One could register the result of NSGetFactory(classID)
, but here is some code creating a Factory manually and registering it.
function Factory(component) {
this.createInstance = function(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return new component();
};
this.register = function() {
Cm.registerFactory(component.prototype.classID,
component.prototype.classDescription,
component.prototype.contractID,
this);
};
this.unregister = function() {
Cm.unregisterFactory(component.prototype.classID, this);
}
Object.freeze(this);
this.register();
}
var factory = new Factory(DuckDuckGoProtocolHandler);
Please note that in restartless add-ons you'll also need to unregister it
again on shutdown!
factory.unregister();
Testing in a Scratchpad
Copy the component code and the manual registration code into a scratchpad, set the Enviroment to Browser, and run it. Then open ddg:some search terms
in a tab ;)