Dmitrii Sorin
Dmitrii Sorin

Reputation: 4016

nsIProtocolHandler and nsIURI: Relative URLs in self-created protocol

I have a simple implementation of custom protocol. It's said that newURI method takes 3 arguments (spec, charset & baseURI) and "if the protocol has no concept of relative URIs, third parameter is ignored".

So i open a page like this tada://domain/samplepage which has XML starting with this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Product SYSTEM "product.dtd">

But i don't see any request regarding product.dtd to my protocol (newURI is not even called). Do i miss smth in my implementation? BTW: the page itself opens correctly, but there's no request to the DTD-file.

const
    Cc = Components.classes,
    Ci = Components.interfaces,
    Cr = Components.results,
    Cu = Components.utils,
    nsIProtocolHandler = Ci.nsIProtocolHandler;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

function TadaProtocol() {
}

TadaProtocol.prototype = {
    scheme: "tada",
    protocolFlags: nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,

    newURI: function(aSpec, aOriginCharset, aBaseURI) {
        let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
        uri.spec = (aBaseURI === null)
            ? aSpec
            : aBaseURI.resolve(aSpec);

        return uri;
    },

    newChannel: function(aURI) {
        let
            ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService),
            uri = ioService.newURI("chrome://my-extension/content/about/product.xml", null, null);

        return ioService.newChannelFromURI(uri);
    },

    classDescription: "Sample Protocol Handler",
    contractID: "@mozilla.org/network/protocol;1?name=tada",
    classID: Components.ID('{1BC90DA3-5450-4FAF-B6FF-F110BB73A5EB}'),
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
}

let NSGetFactory = XPCOMUtils.generateNSGetFactory([TadaProtocol]);

Upvotes: 1

Views: 843

Answers (3)

Dmitrii Sorin
Dmitrii Sorin

Reputation: 4016

Boris and Wladimir, thank you!

After some time i have a solution. The problem was that the DTD-file could not be loaded from my custom-created protocol. The idea was to use Proxy API to override schemeIs() method, which was called in newURI method of nsIProtocolHandler.

So now i have this snippet of code in newURI method:

let standardUrl = Cc["@mozilla.org/network/standard-url;1"].createInstance(Ci.nsIStandardURL);
standardUrl.init(standardUrl.URLTYPE_STANDARD, -1, spec, charset, baseURI);
standardUrl.QueryInterface(Ci.nsIURL);

return Proxy.create(proxyHandlerMaker(standardUrl));

proxyHandlerMaker just implements Proxy API and overrides the needed schemeIs() method. This solved the problem and now all the requests come to newChannel where we can handle them.


Important notes:

  1. Request to DTD comes to newURI() method and does not come to newChannel(). This is the default behavior. This happens because schemeIs("chrome") method is called on the object which was returned by newURI() method. This method should return "true" for DTD-requests if you want the request to reach the newChannel() method.
  2. newChannel() method is invoked with the {nsIURI} object which is not the same as the object which was returned by the newURI method.
  3. If you want to handle both protocol:page & protocol://domain/page URLs by your protocol, you should use both {nsIURI} and {nsIStandardURL} objects
  4. You can pass the created {nsIStandardUrl}-object (standardUrl in the snippet above) as a 2nd argument to the Proxy.create() function. This will make your baseURI (3rd arguments in newURI) pass "baseURI instanceof nsIStandardUrl" check. SchemeIs() method of this proxied object will also return true for the DTD-files requests. But unfortunately the requests won't reach newChannel() method. This could be a nice DTD-problem solution but I can't solve this problem.

Upvotes: 1

Wladimir Palant
Wladimir Palant

Reputation: 57651

As Boris mentioned in his answer, your protocol implementation doesn't set nsIChannel.originalURI property so that URLs will be resolved relative to the chrome: URL and not relative to your tada: URL. There is a second issue with your code however: in Firefox loading external DTDs only works with chrome: URLs, this check is hardcoded. There is a limited number of supported DTDs that are mapped to local files (various HTML doctypes) but that's it - Gecko doesn't support random URLs in <!DOCTYPE>. You can see the current logic in the source code. The relevant bug is bug 22942 which isn't going to be fixed.

Upvotes: 1

Boris Zbarsky
Boris Zbarsky

Reputation: 35064

The channel you return from newChannel has the chrome:// URI you passed to newChannelFromURI as its URI. So that's the URI the page has as its URI, and as its base URI. So the DTD load happens from "chrome://my-extension/content/about/product.dtd" directly.

What you probably want to do is to set aURI as the originalURI on the channel you return from newChannel.

Upvotes: 1

Related Questions