/*********************************************************** constants ***********************************************************/ // The interface we implement - nsIProtocolHandler const nsIProtocolHandler = Components.interfaces.nsIProtocolHandler; // Interfaces that we require const nsISupports = Components.interfaces.nsISupports; const nsIIOService = Components.interfaces.nsIIOService; const nsIURI = Components.interfaces.nsIURI; const nsIURL = Components.interfaces.nsIURL; const nsIRequest = Components.interfaces.nsIRequest; const nsIRequestObserver = Components.interfaces.nsIRequestObserver; const nsIChannel = Components.interfaces.nsIChannel; const nsIHttpChannel = Components.interfaces.nsIHttpChannel; const nsIStreamListener = Components.interfaces.nsIStreamListener; // UUID uniquely identifying our component // You can get from: http://kruithof.xs4all.nl/uuid/uuidgen here const CLASS_ID = Components.ID("{ea00b610-215a-11db-a98b-0800200c9a66}"); // textual unique identifier const CONTRACT_ID = "@mozilla.org/network/protocol;1?name=xri"; // Components that we require const CID_URI = "@mozilla.org/network/simple-uri;1"; const kIOSERVICE_CID_STR = "{9ac9e770-18bc-11d3-9337-00104ba0fd40}"; const CID_URL = "@mozilla.org/network/standard-url;1"; // description const CLASS_NAME = "XRI Protocol Handler"; const PROXY_URI = "http://xri.net/"; const XP_ANY_TYPE = 0; const XP_NUMBER_TYPE = 1; const XP_STRING_TYPE = 2; const XP_BOOLEAN_TYPE = 3; const XP_UNORDERED_NODE_ITERATOR_TYPE = 4; const XP_ORDERED_NODE_ITERATOR_TYPE = 5; const XP_UNORDERED_NODE_SNAPSHOT_TYPE = 6; const XP_ORDERED_NODE_SNAPSHOT_TYPE = 7; const XP_ANY_UNORDERED_NODE_TYPE = 8; const XP_FIRST_ORDERED_NODE_TYPE = 9; var SERVICE_CLASSES = { 'xri://+i-service*(+contact)*($v*1.0)': 'i-contact', 'http://openid.net/signon/1.0': 'openid', 'xri://$res*auth*($v*2.0)': 'res-auth', 'xri://+i-service*(+authn)*(+saml)*($v*1.0)': 'authn-saml', 'xri://+i-service*(+metadata)*(+saml)*($v*1.0)' : 'metadata-saml', 'xri://+i-service*(+forwarding)*($v*1.0)': 'i-forwarding' }; const HTML_HEAD = "\n\ \n\ \n\ FoXRI Explorer - #QXRI#\n\ \n\ \n\

FoXRI Explorer

\n\
\n"; const HTML_FOOT = "
\n\ \n\ "; /// Generic object method wrapper function methodWrapper(obj, method) { return ( function() { /* pass it this inner closure's arguments */ obj[method](arguments); } ); } /// XRDS utility functions var nsResolver = { lookupNamespaceURI: function(prefix) { if (prefix == "xrds") return "xri://$xrds"; else if (prefix == "xrd") return "xri://$xrd*($v*2.0)"; return ""; } }; function runExpr(doc, context, expr, returnType) { if (!returnType) returnType = XP_ANY_TYPE; var res = doc.evaluate(expr, context, nsResolver, returnType, null); return res; } function getNumeric(doc, context, expr) { var res = runExpr(doc, context, expr, XP_NUMBER_TYPE); if (res) return res.numberValue; return null; } function getString(doc, context, expr) { // var res = runExpr(doc, context, expr, XPathResult.STRING_TYPE); var res = runExpr(doc, context, expr, XP_STRING_TYPE); if (res) return res.stringValue; return null; } function getNode(doc, context, expr) { var res = runExpr(doc, context, expr, XP_FIRST_ORDERED_NODE_TYPE); if (res) return res.singleNodeValue; return null; } function getFinalXRD(doc) { var lastNode = doc.firstChild; while (true) { var node = getNode(doc, lastNode, "xrds:XRDS[position()=last()]"); if (!node) break; lastNode = node; } return getNode(doc, lastNode, "xrd:XRD[position()=last()]"); } function isIName(xri) { if (xri.match('^xri://.!', 'i')) { return false; } if (xri.match('^.!', 'i')) { return false; } return true; } function arraySearch(a, re) { var returnArr = new Array(); var i; for (i = 0; i < a.length; i++) { if (a[i].match(re)) { returnArr.push(a[i]); } } return returnArr; } function renderService(srv, doc, qxri) { var html_types = ''; var html_paths = ''; var html_mediatypes = ''; var html_uris = ''; var html_actions = ''; var serviceName = friendlyServiceName(null); var serviceType; // the last non-null Type var knownServiceType; // first recognized service type // get the types var res = runExpr(doc, srv, "xrd:Type/text()"); var t; while (t = res.iterateNext()) { if (t.nodeValue) { if (!knownServiceType && isKnownServiceType(t.nodeValue)) { knownServiceType = t.nodeValue; } serviceType = t.nodeValue; html_types += "Type: " + t.nodeValue + "
"; } } // get the paths res = runExpr(doc, srv, "xrd:Path/text()"); var p; var qxri_prefix = qxri; if (qxri_prefix.charAt(qxri_prefix.length - 1) != '/') { qxri_prefix += '/'; } while (p = res.iterateNext()) { if (p.nodeValue) { html_paths += "Path: " + p.nodeValue + " [ " + qxri_prefix + p.nodeValue + " ]" + "
\n"; } } // get the mediatypes mediaTypes = new Array(); res = runExpr(doc, srv, "xrd:MediaType/text()"); var m; while (m = res.iterateNext()) { if (!knownServiceType) { var srvType = guessServiceTypeByMime(m.nodeValue); knownServiceType = srvType? srvType : null; } mediaTypes.push(m.nodeValue); if (m.nodeValue) { html_mediatypes += "Media Type: " + m.nodeValue + "
"; } } res = runExpr(doc, srv, "xrd:URI/text()"); var u; while (u = res.iterateNext()) { if (!u.nodeValue) continue; var srvType = guessServiceTypeByURI(u.nodeValue); if (!knownServiceType) { knownServiceType = srvType; } html_uris += "
"; var linkContent = u.nodeValue; var uriParts = u.nodeValue.match('^(.*):(.*)$'); if (!uriParts) continue; if (uriParts[1] == 'data') { uriParts = uriParts[2].match('^(.*/.*),(.*)'); if (uriParts && uriParts[1].match('^image/', 'i')) { linkContent = ""; } else if (uriParts) { linkContent = uriParts[1] + " data"; } } else if (uriParts[1] == 'skype') { uriParts = uriParts[2].match('^(.*)\\?(.*)'); if (uriParts) { if (uriParts[2] == "call") { linkContent = "\"Call"; } else if (uriParts[2] == "chat") { linkContent = "\"Chat"; } else if (uriParts[2] == "add") { linkContent = "\"Add"; } } } else if (uriParts[1] == 'aim') { uriParts = uriParts[2].match('^(.*)\\?.*screenname=([^&]*)', 'i'); if (uriParts) { linkContent = "\"Chat Chat with " + uriParts[2]; } } html_uris += "" + linkContent + ""; html_uris += "
"; } var html = "
\n"; html += html_types; html += html_paths; html += html_mediatypes; if (html_uris) { html += "URI(s):
\n"; html += html_uris; } html += "
"; return html; } function isKnownServiceType(type) { if (type.toLowerCase() in SERVICE_CLASSES) { return true; } return false; } function getServiceClass(type) { if (isKnownServiceType(type)) { return SERVICE_CLASSES[type.toLowerCase()]; } return type; } function guessServiceTypeByURI(uri) { if (uri == null || uri == "") { return "unknown"; } if (uri.match(/^https?:/i)) { return "www"; } else if (uri.match(/^skype:/i)) { return "skype"; } else if (uri.match(/^aim:/i)) { return "aim"; } else if (uri.match(/^xmpp:/i)) { return "jabber"; } else if (uri.match(/^tel:/i)) { return "tel"; } else if (uri.match(/^callto:/i)) { return "callto"; } else if (uri.match(/^telnet:/i)) { return "telnet"; } else if (uri.match(/^news:/i)) { return "news"; } else if (uri.match(/^nntp:/i)) { return "nntp"; } else if (uri.match(/^ftp:/i)) { return "ftp"; } else if (uri.match(/^mailto:/i)) { return "email"; } else if (uri.match(/^urn:/i)) { return "urn"; } else if (uri.match(/^data:/i)) { return "data"; } else if (uri.match(/^feed:/i)) { return "feed"; } return "unknown"; } function guessServiceTypeByMime(mimeType) { if (mimeType.match(/^application\/(rss|atom)\+xml/i)) { dump("feed detected!\n"); return "feed"; } else if (mimeType.match(/^image\//i)) { return "image"; } return null; } function friendlyServiceName(srvType, uri) { if (srvType && srvType == "xri://+i-service*(+contact)*($v*1.0)") { return "Contact Service"; } else if (srvType && srvType == "http://openid.net/signon/1.0") { return "OpenID Authentication Service"; } else if (srvType && srvType == "xri://$res*auth*($v*2.0)") { return "Authority Resolution Service"; } else { if (uri == null) { return "Generic Service"; } if (uri.match(/^https?:/i)) { return "Web Link"; } else if (uri.match(/^skype:/i)) { var user = uri.substring("skype:".length, uri.indexOf('?')); return "Skype "; } else if (uri.match(/^mailto:/i)) { var qmark = uri.indexOf('?'); var email = (qmark == -1)? uri.substr("mailto:".length) : uri.substring("mailto:".length, qmark); return "Email (address: " + email + ")"; } else if (srvType != null) { return srvType; // return verbatim } return "Generic Service"; } } function subHTML(template, vars) { for (key in vars) { template = template.replace(key, vars[key], 'g'); } return template; } /// Given the completed XMLHttpRequest object, renders the XRDS function renderXRDS(xmlDoc) { var x = xmlDoc; var qxri = getString(x, x, "/xrds:XRDS/@ref"); var html = subHTML(HTML_HEAD, { '#QXRI#': qxri }); // TODO: render parents as well var lastNode = getFinalXRD(x); if (lastNode) { var stat = getString(x, lastNode, "xrd:Status/@code"); if (stat == "100") { html += "

Exploring " + qxri + "

"; } else { var msg = getString(x, lastNode, "xrd:Status/text()"); html += "

" + qxri + " failed to resolve (reason: " + stat + " - " + msg + ")

"; } html += "
"; var services = runExpr(x, lastNode, "xrd:Service"); var s; var count = getNumeric(x, lastNode, "count(xrd:Service)"); if (count > 0) { while (s = services.iterateNext()) { count++; html += renderService(s, x, qxri); } } else if (stat == '222') { var xriType = isIName(qxri)? 'I-name' : 'I-number'; html += "

" + xriType + " does not exist.

\n"; } else { html += "

No service has been configured for this XRI

"; } } html += ""; return html; } /*********************************************************** XriServiceExplorer class definition ***********************************************************/ function XRIChannel(uri) { this.URI = uri; var r = uri.spec.indexOf('#'); if (r >= 0) { this.qxri = uri.spec.substring(0, r); this.fragment = uri.spec.substring(r); } else { this.qxri = uri.spec; } }; XRIChannel.prototype = { fragment: null, /* private fields used internally */ qxri: null, xmlRequest: null, renderedHTML: null, scriptableInStream: null, buf: null, mChannel: null, copyFields: function(request) { dump("copyFields(loadFlags=" + request.loadFlags + ")\n"); dump("loadGroup = " + request.loadGroup + "\n"); dump("notificationCallbacks = " + request.notificationCallbacks + "\n"); // copy request fields this.loadFlags = request.loadFlags; this.loadGroup = request.loadGroup; this.name = request.name; this.status = request.status; var channel = request.QueryInterface(nsIChannel); if (channel) { this.contentCharset = channel.contentCharset; this.contentLength = channel.contentLength; this.contentType = channel.contentType; // XXX this.contentType = "text/html"; this.notificationCallbacks = channel.notificationCallbacks; this.originalURI = this.originalURI; this.URI = this.URI; this.owner = channel.owner; this.securityInfo = channel.securityInfo; channel = channel.QueryInterface(nsIHttpChannel); if (channel) { this.allowPipelining = channel.allowPipelining; this.redirectionLimit = channel.redirectionLimit; this.referrer = channel.referrer; this.requestMethod = channel.requestMethod; this.requestSucceeded = channel.requestSucceeded; this.responseStatus = channel.responseStatus; this.responseStatusText = channel.responseStatusText; } } }, /* nsIStreamListener */ asyncOpenListener: null, /* nsISupports (but we really don't care) */ asyncOpenContext: null, /* has the XML finished loading? */ loadDone: false, /* public fields (nsIStreamListener implementation) */ onDataAvailable : function(request, ctx, inputStream, offset, count) { dump("\nonDataAvailable, offset=" + offset + ", count=" + count + "\n"); // XXX /* this.copyFields(request); this.asyncOpenListener.onDataAvailable(this, this.asyncOpenContext, inputStream, offset, count); return; */ if (offset == 0) { this.scriptableInStream.init(inputStream); } this.buf += this.scriptableInStream.read(count); if (!request.isPending()) { dump("request finished, buf = " + this.buf + "\n"); this.scriptableInStream = null; } else { dump("request pending...\n"); dump("buf so far = " + this.buf + "\n"); } }, /* public fields (nsIRequestObserver implementation) */ onStartRequest : function(request, ctx) { dump("\nonStartRequest called\n"); // XXX this.copyFields(request); this.asyncOpenListener.onStartRequest(this, this.asyncOpenContext); }, onStopRequest : function(request, ctx, status) { dump("\nonStopRequest called - status " + status + "\n"); // XXX /* this.asyncOpenListener.onStopRequest(this, this.asyncOpenContext, status); return; */ this.copyFields(request); this.loadDone = true; if (status == 0) { var domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser); var xmlDoc = domParser.parseFromString(this.buf, "text/xml"); // make fake inputstream var renderedHTML = renderXRDS(xmlDoc); this.contentCharset = "UTF-8"; this.contentLength = renderedHTML.length; this.contentType = "text/html"; dump("rendered HTML = \n" + renderedHTML + "\n"); dump("\nCalling asyncOpenListener.onStartRequest\n\n"); var strIStream = Components.classes["@mozilla.org/io/string-input-stream;1"].createInstance(Components.interfaces.nsIStringInputStream); if (strIStream) { strIStream.setData(renderedHTML, renderedHTML.length); /* strIStream.setData(this.buf, this.buf.length); */ dump("\nleftovers in string-input-stream = " + strIStream.available() + "\n"); dump("\nCalling asyncOpenListener.onDataAvailable\n\n"); this.asyncOpenListener.onDataAvailable(this, this.asyncOpenContext, strIStream, 0, renderedHTML.length); /* this.asyncOpenListener.onDataAvailable(this, this.asyncOpenContext, strIStream, 0, this.buf.length); */ dump("\nleftovers in string-input-stream = " + strIStream.available() + "\n"); } } else { dump("\nStatus = " + status + "\n"); dump("Calling asyncOpenListener.onStartRequest\n\n"); this.asyncOpenListener.onStartRequest(this, this.asyncOpenContext); } dump("stopping request for underlying asyncOpenListener\n"); this.asyncOpenListener.onStopRequest(this, this.asyncOpenContext, status); // copied from nsIWyciwygChannel this.asyncOpenListener = null; this.asyncOpenContext = null; /* if (this.loadGroup) { this.loadGroup.removeRequest(request, null, status); } */ this.notificationCallbacks = null; this.mChannel = null; dump("stopped request\n"); }, /* public fields (nsIInputStream implementation) */ available: function() { dump("nsIInputStream::available called\n"); return renderedHTML.length; }, close: function() { dump("nsIInputStream::close called\n"); }, isNonBlocking: function() { dump("nsIInputStream::isNonBlocking called\n"); return true; }, read: function() { dump("nsIInputStream::read() called!!!\n"); }, /* public fields (nsIRequest implmentation) */ loadFlags: 0, loadGroup: null, name: "xri://request", status: 0, cancel: function(status) { dump("\ncancel called...\n"); }, isPending: function() { dump("isPending called\n\n"); return !this.loadDone; }, resume: function() { dump("resume called\n"); }, suspend: function() { dump("suspend called\n"); }, /* public fields (nsIChannel implmentation) */ contentCharset: null, contentLength: -1, contentType: null, notificationCallbacks: null, originalURI: null, owner: null, securityInfo: null, URI: null, open: function() { dump("open not supporteD!!!!!!\n"); }, asyncOpen: function(listener, context) { dump("asyncOpen called!!!!!!\n"); this.asyncOpenListener = listener; this.asyncOpenContext = context; var hxri = PROXY_URI + this.qxri + "?_xrd_r=application/xrds%2Bxml;sep=false"; var ioService = Components.classesByID[kIOSERVICE_CID_STR].getService(); ioService = ioService.QueryInterface(nsIIOService); var channel = ioService.newChannel(hxri, null, null); if (this.scriptableInStream) { dump("Hey! You can't possibly be reusing this handler?!\n"); return; } dump("making scriptableInStream\n"); this.scriptableInStream = Components.classes["@mozilla.org/scriptableinputstream;1"] .createInstance(Components.interfaces.nsIScriptableInputStream); this.buf = ''; dump("notificationCallbacks = " + this.notificationCallbacks + "\n"); dump("loadFlags = " + this.loadFlags + "\n"); dump("loadGroup = " + this.loadGroup + "\n"); dump("owner = " + this.owner + "\n"); dump("securityInfo = " + this.securityInfo + "\n"); // these nsIRequest attributes must be copied to the stub // channel that we created channel.notificationCallbacks = this.notificationCallbacks; channel.loadGroup = this.loadGroup; channel.loadFlags = this.loadFlags; this.mChannel = channel; channel.asyncOpen(this, null); }, /* public fields (nsIChannel implmentation) */ allowPipelining: false, redirectionLimit: 5, referrer: "", requestMethod: "GET", requestSucceeded: true, responseStatus: 200, responseStatusText: "OK", getRequestHeader: function(header) { dump("getRequestHeader(" + header + ")\n"); var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); try { var val = httpChannel.getRequestHeader(header); dump("getRequestHeader(" + header + ") = " + val + "\n"); return val; } catch (e) { throw e; } }, getResponseHeader: function(header) { dump("getResponseHeader(" + header + ")\n"); var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); try { var val = httpChannel.getResponseHeader(header); dump("getResponseHeader(" + header + ") = " + val + "\n"); return val; } catch (e) { throw e; } /* XXX if (header == "Content-Type") return "text/html"; */ return null; }, isNoCacheResponse: function() { dump("isNoCacheResponse()\n"); var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); return httpChannel.isNoCacheResponse(); }, isNoStoreResponse: function() { dump("isNoStoreResponse()\n"); var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); return httpChannel.isNoStoreResponse(); return true; }, setRequestHeader: function(header, value, merge) { dump("setRequestHeader(" + header + ", " + value + ")\n"); var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); return httpChannel.setRequestHeader(header, value, merge); }, setResponseHeader: function(header, value, merge) { dump("setResponseHeader(" + header + ", " + value + ")\n"); var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); return httpChannel.setResponseHeader(header, value, merge); }, visitRequestHeaders: function(visitor) { dump("visitRequestHeaders()\n"); }, visitResponseHeaders: function(visitor) { dump("visitResponseHeaders()\n"); }, QueryInterface: function(iid) { dump("QI.. \n"); if (iid.equals(nsIChannel)) dump("QI(nsIChannel)\n"); else if (iid.equals(nsIHttpChannel)) dump("QI(nsIHttpChannel)\n"); else if (iid.equals(Components.interfaces.nsIUploadChannel)) dump("QI(nsIUploadChannel) - not supported\n"); else if (iid.equals(Components.interfaces.nsICachingChannel)) dump("QI(nsICachingChannel) - not supported\n"); else if (iid.equals(Components.interfaces.nsIClassInfo)) dump("QI(nsIClassInfo) - not supported\n"); else if (iid.equals(Components.interfaces.nsISecurityCheckedComponent)) dump("QI(nsISecurityCheckedComponent) - not supported\n"); else if (iid.equals(Components.interfaces.nsIWyciwygChannel)) dump("QI(nsIWyciwygChannel) - not supported\n"); else if (iid.equals(Components.interfaces.nsIMultiPartChannel)) dump("QI(nsIMultiPartChannel) - not supported\n"); else if (iid.equals(Components.interfaces.nsIHttpChannelInternal)) dump("QI(nsIHttpChannelInternal) - not supported\n"); else if (iid.equals(Components.interfaces.nsIWritablePropertyBag2)) dump("QI(nsIWritablePropertyBag2) - not supported\n"); else if (iid.equals(nsIRequest)) dump("QI(nsIRequest)\n"); else if (iid.equals(nsIRequestObserver)) dump("QI(nsIRequestObserver)\n"); else if (iid.equals(nsISupports)) dump("QI(nsISupports)\n"); else if (iid.equals(nsIStreamListener)) dump("QI(nsIStreamListener)\n"); else dump("unknown " + iid + "\n"); if (iid.equals(nsISupports) || iid.equals(nsIRequest) || iid.equals(nsIRequestObserver) || iid.equals(nsIChannel) || iid.equals(nsIHttpChannel) || iid.equals(nsIStreamListener) ) { return this; } throw Components.results.NS_ERROR_NO_INTERFACE; } }; /*********************************************************** XriProtocolHandler class definition ***********************************************************/ //class constructor function XriProtocolHandler() { }; // class definition XriProtocolHandler.prototype = { defaultPort: 80, // HTTP protocolFlags : nsIProtocolHandler.ALLOWS_PROXY | nsIProtocolHandler.ALLOWS_PROXY_HTTP, scheme: "xri", allowPort: function() { return false; // only called for blacklisted ports, should respect }, _newHttpChannel: function(aURI) { var HXRI = PROXY_URI + aURI.spec; var ioService = Components.classesByID[kIOSERVICE_CID_STR].getService(); ioService = ioService.QueryInterface(nsIIOService); var channel = ioService.newChannel(HXRI, null, null); return channel; }, newChannel: function(aURI) { // leave alone if path is not empty or just a single slash or query exists dump("path='" + aURI.path + "'\n"); dump("query='" + aURI.query + "'\n"); dump("spec='" + aURI.spec + "'\n"); var slashPos = aURI.spec.indexOf('/', 'xri://'.length); var qmarkPos = aURI.spec.indexOf('?'); dump("slashPos='" + slashPos + "'\n"); dump("qmarkPos='" + qmarkPos + "'\n"); if ((slashPos > 0 && slashPos < aURI.spec.length - 1) || qmarkPos > -1) { return this._newHttpChannel(aURI); } var explorer = new XRIChannel(aURI); return explorer; }, newURI: function(spec, originCharset, baseURI) { var newSpec = spec; if (baseURI != null) { // standard-url (nsIURL) does not work with @-GCS var baseURL = Components.classes[CID_URL].createInstance(nsIURL); baseURL.spec = baseURI.spec; newSpec = baseURL.resolve(spec); } var uri = Components.classes[CID_URI].createInstance(nsIURI); uri.spec = newSpec; return uri; }, QueryInterface: function(aIID) { if (!aIID.equals(nsIProtocolHandler) && !aIID.equals(nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; /*********************************************************** class factory This object is a member of the global-scope Components.classes. It is keyed off of the contract ID. Eg: myXriProtocolHandler = Components.classes["@dietrich.ganx4.com/helloworld;1"]. createInstance(Components.interfaces.nsIXriProtocolHandler); ***********************************************************/ var XriProtocolHandlerFactory = { createInstance: function (aOuter, aIID) { if (aOuter != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new XriProtocolHandler()).QueryInterface(aIID); } }; /*********************************************************** module definition (xpcom registration) ***********************************************************/ var XriProtocolHandlerModule = { _firstTime: true, registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { if (this._firstTime) { this._firstTime = false; throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; } aCompMgr = aCompMgr. QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); }, unregisterSelf: function(aCompMgr, aLocation, aType) { aCompMgr = aCompMgr. QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation); }, getClassObject: function(aCompMgr, aCID, aIID) { if (!aIID.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; if (aCID.equals(CLASS_ID)) return XriProtocolHandlerFactory; throw Components.results.NS_ERROR_NO_INTERFACE; }, canUnload: function(aCompMgr) { return true; } }; /*********************************************************** module initialization When the application registers the component, this function is called. ***********************************************************/ function NSGetModule(aCompMgr, aFileSpec) { return XriProtocolHandlerModule; }