Create a Firefox Addon to View and Modify XHR Requests and Responses

Update: I think the subject gave the wrong idea that I'm looking for an existing addon. This is a common problem and I DO NOT want an existing solution.
I want to use WRITE (or a more suitable, modifying and existing) Addon.

Here is my requirement:

  • I want my addon to work only for a specific site
  • The data on the pages is encoded using a 2-way hash
  • Most of the information is loaded with XHR requests, and sometimes displayed in animated bubbles, etc.
  • The current version of my addon parses the page through XPath expressions, decodes the data and replaces it

  • The problem arises with those bubble boxes that are displayed on hover

  • So I realized that it might be a good idea to create an XHR bridge that could listen to all the data and decode / encode on the fly
  • After several searches, I came across nsITraceableInterface [1] [2] [3]

Just wanted to know if I'm right. If yes, then kindly provide any additional pointers and suggestions that may be relevant; and if "No", then .. well, please help with the correct pointers :)

Thanks,
Bipin.

[one]. https://developer.mozilla.org/en/NsITraceableChannel
[2]. http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
[3]. http://www.ashita.org/howto-xhr-listening-by-a-firefox-addon/

+6
javascript firefox-addon xpcom
source share
3 answers

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()

+8
source share
0
source share

You can try to create a greasemonkey script and overwrite XMLHttpRequest.
The code will look something like this:

  function request () {
 };
 request.prototype.open = function (type, path, block) {
  GM_xmlhttpRequest ({
   method: type,
   url: path,
   onload: function (response) {
    // some code here
   }
  });
 };
 unsafeWindow.XMLHttpRequest = request;

Also note that you can turn a GM script into an addon for Firefox

0
source share

All Articles