javascriptfirefox-addonxpcom

Use registerProtocolHandler without contentWindow


I'm trying to install hotmail to my mailto handlers:

Image

This is accomplished with this code from webpage scope:

navigator.registerProtocolHandler('mailto','http://mail.live.com/secure/start?action=compose&to=%s','Live Mail');

From XPCOM we would have to use this: MXR ::WebContentConverter.js#369) .

So from searching github code I figure you import it like this:

var nsiwchr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].getService(Ci.nsIWebContentHandlerRegistrar);

So I would register it like this:

nsiwchr.registerProtocolHandler('mailto','http://mail.live.com/secure/start?action=compose&to=%s','Live Mail', null);

I use null because I don't have a contentWindow but apparently you can't pass null for this. Because:

http://mxr.mozilla.org/mozilla-release/source/browser/components/feeds/src/WebContentConverter.js#372

var uri = this._checkAndGetURI(aURIString, aContentWindow);

And then it tests:

aContentWindow.location.hostname != uri.host)

So I figured to fake it out like this:

var fakeContentWindow = {
  document: {
    baseURIObject: {
      asciiHost:"mail.live.com",
      asciiSpec:"http://mail.live.com/secure",
      hasRef:true,
      host:"mail.live.com",
      hostPort:"mail.live.com",
      originCharset:"UTF-8",
      password:"",
      path:"/secure",
      port:-1,
      prePath:"http://mail.live.com",
      ref:"", //369
      scheme:"http",
      spec:"http://mail.live.com/secure",
      specIgnoringRef:"http://mail.live.com",
      userPass:"",
      username:""
    }
  },
  location: {
    hash:"", //#369
    host:"mail.live.com",
    hostname:"mail.live.com",
    href:"http://mail.live.com/secure",
    origin:"http://mxr.mozilla.org",
    pathname:"/secure",
    port:"",
    protocol:"http:",
    search:""
  }
};

nsiwchr.registerProtocolHandler('mailto','http://mail.live.com/secure/start?action=compose&to=%s','Live Mail', fakeContentWindow);

but it throws some totally weird error:

/* Exception: [object XPCWrappedNative_NoHelper] */ Browser Console throws:

"[object XPCWrappedNative_NoHelper]" scratchpad.js:999
SP_writeAsErrorComment/<() scratchpad.js:999
Handler.prototype.process() Promise-backend.js:863
this.PromiseWalker.walkerLoop() Promise-backend.js:742

This makes no sense. I want to successfully spoof this without using a real contentWindow.


Solution

  • You simply cannot use that high-level API without a window (and you probably don't want to use it anyway, because it will not actually add the handler, but show a UI notification that first asks the user to add it; which is not only not what you want, but also won't work because there is no UI to display that notification in the first place).

    Instead, you'll want to create your own version based on the implementation that omits all those security checks, UI notifications, etc.

    Based on registerNotificiation, it would look something like this.

    var protocolScheme = "mailtoorsomething";
    var uri = Services.io.newURI("someuri?with_%s_replacement", null, null);
    var name = "Some Name";
    var desc = "Some description";
    
    var protocolHandler = Services.io.getProtocolHandler(protocolScheme);
    if (!(protocolHandler instanceof Ci.nsIExternalProtocolHandler)) {
      throw new Error("Cannot register handler for built-in protocol");
    }
    
    var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
              getService(Ci.nsIExternalProtocolService);
    var handlerInfo = eps.getProtocolHandlerInfo(protocolScheme);
    var handlers =  handlerInfo.possibleApplicationHandlers;
    for (let i = 0; i < handlers.length; i++) {
      let h = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
      if (h.uriTemplate == uri.spec) {
        throw new Error("Already registered");
      }
    }
    
    var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
                  createInstance(Ci.nsIWebHandlerApp);
    handler.name = name;
    handler.detailedDescription = desc;
    handler.uriTemplate = uri.spec;
    handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
    
    handlerInfo.alwaysAskBeforeHandling = false;
    handlerInfo.preferredApplicationHandler = handler;
    handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
    
    var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
              getService(Ci.nsIHandlerService);
    hs.store(handlerInfo);