Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Noitidart/2faaac70c62bc13e7773 to your computer and use it in GitHub Desktop.
Save Noitidart/2faaac70c62bc13e7773 to your computer and use it in GitHub Desktop.
_ff-addon-tutorial-CheckGetSetRemoveAddHandlerOfProtocol - Shows how to get and check possible handlers for a protocol and how to set it to something or remove it.

Get the handlerInfo object

nsIHandlerService Method

The handlerInfo object returned here is exactly same as doing it with the above method of nsiExternalProtocolService.

If you go to the options panel and change to alwaysAsk the alwaysAskBeforeHandling property in the log handlerInfos will reflect properly.

var handlerService = Cc['@mozilla.org/uriloader/handler-service;1'].getService(Ci.nsIHandlerService);
var listOfWrappedHandlers = handlerService.enumerate();
var i = 0;
while (listOfWrappedHandlers.hasMoreElements()) {
  var handlerInfo = listOfWrappedHandlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
  //console.log(i, 'handler for', wrappedHandlerInfo.type, wrappedHandlerInfo);
  if (handlerInfo.type == 'mailto') {
    break;
  }
  i++;
}
console.log('handlerServicehandlerInfo=', handlerInfo); //is the mailto one as we broke the loop once it found that

Does not have possibleLocalHandlers in handlerInfo.

nsiExternalProtocolService Method

The handlerInfo object returned here is exactly same as doing it with the above method of nsIHandlerService.

If you go to the options panel and change to alwaysAsk the alwaysAskBeforeHandling property in the log handlerInfos will reflect properly.

var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo('mailto');
console.log('epsHandlerInfo', handlerInfo)

Does not have possibleLocalHandlers in handlerInfo.

nsIMIMEService Method

note: has major quirks, like not accurately refelect preferedHandlerand alwaysAskBeforeHandling (i just noticed these two so far I didnt explore other properties but they may be equally jacked up

Using the top two methods of nsIHandlerService and nsiExternalProtocolService, if you go to the options panel and change to alwaysAsk the alwaysAskBeforeHandling property in the log handlerInfos will reflect properly. However if you get handlerInfo with this method, nsIMIMEService, then it doesnt accurately reflect the drop down setting from alwaysAskBeforeHandling its so weird:

It also has possibleApplicationHandlers which is length of 0. Odd. It also has possibleLocalHandlers which has a length of 53 in my case, interesting.

var mimeService = Cc['@mozilla.org/mime;1'].getService(Ci.nsIMIMEService);
var CONTENT_TYPE = 'mailto';
var TYPE_EXTENSION = '';

var handlerInfo = mimeService.getFromTypeAndExtension(CONTENT_TYPE, TYPE_EXTENSION);
console.info('mimeServiceHandlerInfo:', handlerInfo); //http://i.imgur.com/dUKox24.png

This mimeServiceHandlerInfo also has different object keys from the handlerInfo returned by the top two methods of EPS and HandlerService.

Check the handlers that are associated with this

Using handlerInfo from any of the three methods above (well I haven't yet tested nsiMIMService's handlerInfo but I'm guessing it should work)

queryElementAt Method

This way does not list the "Microsoft Outlook", it only lists the gmail, ymail and things i added, weird

     var handlers = handlerInfo.possibleApplicationHandlers;
     console.log('handlers', handlers)
     for (var i = 0; i < handlers.length; ++i) {
       var handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp); //instead of Ci.nsIHandlerApp
       console.log('poss handler', i, handler, handler.uriTemplate);

     }

enumerate Method

I don't think this way lists the "Microsoft Outlook" either but I'm not sure as it's only logging small handler objects.

This way only returns/logs small object (handler) for the first non system default handler (i think thats what it is, but it definitely only returns for one). for which it returns the name etc I fixed this problem by adding in .QueryInterface(Ci.nsIHandlerApp)

     var handlers = handlerInfo.possibleApplicationHandlers.enumerate();
     while (handlers.hasMoreElements()) {
       var handler = handlers.getNext().QueryInterface(Ci.nsIWebHandlerApp); //instead of Ci.nsIHandlerApp
       console.log('handler', handler);
     }

Notes on both enumerate and queryElementAt

  • It was QI'ing Ci.nsIHandlerApp which would not have uriTemplate in the handler object, so I changed it to Ci.nsIWebHandlerApp, this retuns in hte object uriTemplate.

  • Can QI handlers in possibleApplicationHandlers with Ci.nsIHandlerApp BUT CANNOT QI handlers in possibleLocalHandlers with Ci.nsIWebHandlerApp it will throw the following exception:

    Exception: Component returned failure code: 0x80004002 (NS_NOINTERFACE) [nsISupports.QueryInterface]
    

Add a handler to a protocol

registerProtocolHandler shows all the checks to make when adding a handler, and it shows how to add it. MXR :: mozilla-release/ mozilla/ browser/ components/ feeds/ src/ WebContentConverter.js #L368 - registerProtocolHandler

This snippet shows how to add a nsIWebAppHandler. I'm not sure how to add a nsILocalAppHandler (im not sure if it's even called nsILocalAppHandler maybe its just (actually probably is) nsIAppHandler). No validation checks are made before adding it here. You should do the following checks before adding:

  1. Check if the handler we're adding already exists as possible handler MXR :: mozilla-release/ mozilla/ browser/ components/ feeds/ src/ WebContentConverter.js #349 - _protocolHandlerRegistered
  • Curious note here: This only checks possibleApplicationHandlers it doesn't do possibleLocalHandlers, i'm not sure why, but probably because we are dealing with a nsIWebAppHandler
  1. Check to make sure the protocol we are adding handler to isn't already handled internally (we don't want to let them take over, say "chrome"). MXR :: mozilla-release/ mozilla/ browser/ components/ feeds/ src/ WebContentConverter.js #390
  2. Check if the protocol we are adding handler to is in the black list. If it's in the blacklist it means it does not want other handlers added to it. I'm not sure how important this is but registerProtocolHandler does it. MXR :: mozilla-release/ mozilla/ browser/ components/ feeds/ src/ WebContentConverter.js #402

So finally here is the code on how to add a handler without doing any of the three checks above.

var protocol = 'mailto';
var name = 'Hotmail Mailto Handler';
var newURIArgs = {
	aURL: 'http://mail.live.com/secure/start?action=compose&to=%s',
	aOriginCharset: null,
	aBaseURI: null
};
var myURI = Services.io.newURI(newURIArgs.aURL, newURIArgs.aOriginCharset, newURIArgs.aBaseURI);
var myURISpec = myURI.spec;


var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(Ci.nsIWebHandlerApp);
handler.name = name;
handler.uriTemplate = myURISpec;

var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo(protocol);
handlerInfo.possibleApplicationHandlers.appendElement(handler, false);

if (handlerInfo.possibleApplicationHandlers.length > 1) {
	// May want to always ask user before handling because it now has multiple possibleApplicationsHandlers BUT dont have to
	//handlerInfo.alwaysAskBeforeHandling = true;
}

var hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
hs.store(handlerInfo);

How to get the currently set handler for a protocol

First get the handlerInfo object for the protocol by using Get the handlerInfo object - nsIHandlerService - Method or Get the handlerInfo object - nsiExternalProtocolService Method.

  1. Then first check if handlerInfo.alwaysAskBeforeHandling is true. If this is the case than the active handler is "Always Ask". If this is the case than handlerInfo.preferredApplicationHandler and handlerInfo.preferredAction are stale. By stale I mean that they are not true, they are set to whatever it was set at before.
  2. handlerInfo.alwaysAskBeforeHandling is false then check handlerInfo.preferredAction.
  • If handlerInfo.preferredAction == Ci.nsIHandlerInfo.useSystemDefault than the handler.preferredApplicaitonHandler is stale in that it is whatever it was set to last time. So the active handler is whatever the userSystemDefault one is. (at the time of this writing August 3rd, 2014 - I'm not sure how to figure out the system default handler for a protocol)
  • If handlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp than default handler is whatever is held in handlerInfo.preferredApplicationHandler. In this case handler.preferredApplicaitonHandler should not be null. In all other cases handler.preferredApplicaitonHandler has the potential to be null.
  • If handlerInfo.preferredAction == Ci.nsIHandlerInfo.saveToDisk than default handler is to download it
  • If handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally than default handler is I don't know as of writing of this (August 3rd, 2014)

Demo/Example - Set the mailto handler to be "Yahoo! Mail"

//start - demo make handler for mailto be y! mail
var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo('mailto');
console.log('epsHandlerInfo', handlerInfo)

var handlers = handlerInfo.possibleApplicationHandlers.enumerate();
var foundYahooMailHandler = false;
while (handlers.hasMoreElements()) {
	var handler = handlers.getNext();
	if (handler.QueryInterface(Ci.nsIWebHandlerApp).uriTemplate == 'https://compose.mail.yahoo.com/?To=%s') { //this is how i decided to indentify if the handler is of yahoo mail
		foundYahooMailHandler = true;
		break;
	}
}

if (foundYahooMailHandler) {
	//it was found. and in the while loop when i found it, i "break"ed out of the loop which left handlerInfo set at the yahoo mail handler
	//set this to the prefered handler as this handler is the y! mail handler
	handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp; //Ci.nsIHandlerInfo has keys: alwaysAsk:1, handleInternally:3, saveToDisk:0, useHelperApp:2, useSystemDefault:4
	handlerInfo.preferredApplicationHandler = handler;
	handlerInfo.alwaysAskBeforeHandling = false;
	var hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
	hs.store(handlerInfo);
} else {
	alert('could not find yahoo mail handler. meaning i couldnt find a handler with uriTemplate of ...compose.mail.yahoo....')
}
//end - demo make handler for mailto be y! mail

Demo/Example of how to remove handler

var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo('mailto');
console.log('epsHandlerInfo', handlerInfo)

console.log('handlerInfo.preferredApplicationHandler', handlerInfo.preferredApplicationHandler);
console.log('handlerInfo.possibleApplicationHandlers', handlerInfo.possibleApplicationHandlers);

var handlers = handlerInfo.possibleApplicationHandlers;
console.log('handlers', handlers)
for (var i = 0; i < handlers.length; ++i) {
	var handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
	console.log('handler', i, handler, handler.uriTemplate);

	if (Services.wm.getMostRecentWindow(null).confirm('delete handler at position ' + i + '? its uriTemplate = "' + handler.uriTemplate + '" and name = "' + handler.name + '"')) {
		if (handler.equals(handlerInfo.preferredApplicationHandler)) {
			Services.wm.getMostRecentWindow(null).alert('the preferredApplicationHandler was the one we are removing now, so null the preferredAppHand and set to always ask');
			//if the last preferredApplicationHandler was this then nullify it, just me trying to keep things not stale
			handlerInfo.preferredApplicationHandler = null;
			if (handlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp) {
				//it looks like the preferredAction was to use this helper app, so now that its no longer there we will have to ask what the user wants to do next time the uesrs clicks a mailto: link
				handlerInfo.alwaysAskBeforeHandling = true;
				handlerInfo.preferredAction = Ci.nsIHandlerInfo.alwaysAsk; //this doesnt really do anything but its just nice to be not stale. it doesnt do anything because firefox checks handlerInfo.alwaysAskBeforeHandling to decide if it should ask. so me doing this is just formality to be looking nice
			}
		}
		handlers.removeElementAt(i);
		i--;
	}
	var hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
	hs.store(handlerInfo);
}
@Noitidart
Copy link
Author

quick snipets i use:

  • list all handlers and their infos for mailto:

    var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
    var handlerInfo = eps.getProtocolHandlerInfo('mailto');
    console.log('epsHandlerInfo', handlerInfo)
    
    var shouldCallStore = false;
    //start - find installed handlers
    var handlers = handlerInfo.possibleApplicationHandlers.enumerate();
    while (handlers.hasMoreElements()) {
        var handler = handlers.getNext();
        console.log('handler', handler)
        try {
           handler.QueryInterface(Ci.nsIWebHandlerApp);
        } catch (ex) {
           console.log('this handler is not a web handler app');
        }
    
    }
    

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment