nsITraceableChannel is really a way to go here. blog posts by Jan Odvarko (softwareishard.com) and me (ashita.org) show how to do this. You can also see http://www.ashita.org/implementing-an-xpcom-firefox-interface-and-creating-observers/ , however it is not necessary to do this in the XPCOM component.
The steps are basically:
- Prototype an object that implements nsITraceableChannel; and create an observer to listen for http-on-modify-request and http-on-study-response
- register observer
- An observer listening for two types of queries adds our nsITraceableChannel object to the listener chain and ensures that our nsITC knows who is next in the chain
- The nsITC object provides three callbacks and each of them will be called at the appropriate stage: onStartRequest, onDataAvailable and onStopRequest
- in each of the callbacks above, our nsITC object must pass data to the next element in the chain
Below is the actual code from the site-specific add-on that I wrote, which behaves very similar to yours from what I can say.
function TracingListener() { //this.receivedData = []; } TracingListener.prototype = { originalListener: null, receivedData: null, // array for incoming data. onDataAvailable: function(request, context, inputStream, offset, count) { var binaryInputStream = CCIN("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream"); var storageStream = CCIN("@mozilla.org/storagestream;1", "nsIStorageStream"); binaryInputStream.setInputStream(inputStream); storageStream.init(8192, count, null); var binaryOutputStream = CCIN("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream"); binaryOutputStream.setOutputStream(storageStream.getOutputStream(0)); // Copy received data as they come. var data = binaryInputStream.readBytes(count); //var data = inputStream.readBytes(count); this.receivedData.push(data); binaryOutputStream.writeBytes(data, count); this.originalListener.onDataAvailable(request, context,storageStream.newInputStream(0), offset, count); }, onStartRequest: function(request, context) { this.receivedData = []; this.originalListener.onStartRequest(request, context); }, onStopRequest: function(request, context, statusCode) { try { request.QueryInterface(Ci.nsIHttpChannel); if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0) { var data = null; if (request.requestMethod.toLowerCase() == "post") { var postText = this.readPostTextFromRequest(request, context); if (postText) data = ((String)(postText)).parseQuery(); } var date = Date.parse(request.getResponseHeader("Date")); var responseSource = this.receivedData.join(''); //fix leading spaces bug responseSource = responseSource.replace(/^\s+(\S[\s\S]+)/, "$1"); piratequesting.ProcessRawResponse(request.originalURI.spec, responseSource, date, data); } } catch (e) { dumpError(e); } this.originalListener.onStopRequest(request, context, statusCode); }, QueryInterface: function (aIID) { if (aIID.equals(Ci.nsIStreamListener) || aIID.equals(Ci.nsISupports)) { return this; } throw Components.results.NS_NOINTERFACE; }, readPostTextFromRequest : function(request, context) { try { var is = request.QueryInterface(Ci.nsIUploadChannel).uploadStream; if (is) { var ss = is.QueryInterface(Ci.nsISeekableStream); var prevOffset; if (ss) { prevOffset = ss.tell(); ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); } // Read data from the stream.. var charset = "UTF-8"; var text = this.readFromStream(is, charset, true); // Seek locks the file so, seek to the beginning only if necko hasn't read it yet, // since necko doesn't seek to 0 before reading (at lest not till 459384 is fixed). if (ss && prevOffset == 0) ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); return text; } else { dump("Failed to Query Interface for upload stream.\n"); } } catch(exc) { dumpError(exc); } return null; }, readFromStream : function(stream, charset, noClose) { var sis = CCSV("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream"); sis.setInputStream(stream); var segments = []; for (var count = stream.available(); count; count = stream.available()) segments.push(sis.readBytes(count)); if (!noClose) sis.close(); var text = segments.join(""); return text; } } hRO = { observe: function(request, aTopic, aData){ try { if (typeof Cc == "undefined") { var Cc = Components.classes; } if (typeof Ci == "undefined") { var Ci = Components.interfaces; } if (aTopic == "http-on-examine-response") { request.QueryInterface(Ci.nsIHttpChannel); if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0) { var newListener = new TracingListener(); request.QueryInterface(Ci.nsITraceableChannel); newListener.originalListener = request.setNewListener(newListener); } } } catch (e) { dump("\nhRO error: \n\tMessage: " + e.message + "\n\tFile: " + e.fileName + " line: " + e.lineNumber + "\n"); } }, QueryInterface: function(aIID){ if (typeof Cc == "undefined") { var Cc = Components.classes; } if (typeof Ci == "undefined") { var Ci = Components.interfaces; } if (aIID.equals(Ci.nsIObserver) || aIID.equals(Ci.nsISupports)) { return this; } throw Components.results.NS_NOINTERFACE; }, }; var observerService = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); observerService.addObserver(hRO, "http-on-examine-response", false);
In the above code, originalListener is the listener that we insert in front of us in the chain. It is very important that you save this information when creating the Tracing Listener and pass data in all three callbacks. Otherwise, nothing will work (the pages will not even load. Firefox itself is the last in the chain).
Note: in the above code there are some functions that are part of the piratequesting add-in, for example: parseQuery() and dumpError()