views:

1170

answers:

1

In my firefox extension I'm creating a xul:browser element. I want to have an observer that intercepts any url changes within the embedded browser and opens the url in a new browser tab (in the main browser). I'd also like new windows spawned by the xul:browser window to open in a tab instead of a new browser window.

I've created an observer which works, but I don't yet know how to apply that observer only to the xul:browser element.

function myFunction(){
   var container = jQuery("#container")[0];
   var new_browser_element = document.createElement('browser');
   container.appendChild(new_browser_element);

   var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
   observerService.addObserver(myObserver, "http-on-modify-request", false);
}


var myObserver = {
   observe: function(aSubject, aTopic, aData){
   if (aTopic != 'http-on-modify-request'){
   aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
         // alert(aSubject.URI.spec);
        // Now open url in new tab
      } 
   },

   QueryInterface: function(iid){
   if (!iid.equals(Components.interfaces.nsISupports) &&
   !iid.equals(Components.interfaces.nsIObserver))
   throw Components.results.NS_ERROR_NO_INTERFACE;

   return this;
   }
};
+1  A: 

You could try:

var myObserver = {
   observe: function(aSubject, aTopic, aData){
   if (aTopic == 'http-on-modify-request')
              {
                aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
                var url = aSubject.URI.spec;
                var postData ;
                if (aSubject.requestMethod.toLowerCase() == "post") 
                {
                  var postText = this.readPostTextFromRequest(request);
                  if (postText)
                  {
                    var dataString = parseQuery(postText);
                    postData = postDataFromString(dataString);
                  }
                }

                var oHttp = aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);

                var interfaceRequestor =   oHttp.notificationCallbacks.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
                var DOMWindow = interfaceRequestor.getInterface(Components.interfaces.nsIDOMWindow); 


                //check if it is one of your mini browser windows
                if (jQuery(DOMWindow).hasClass('mini_browser')) 
                {

                  openInTab(url, postData);

                  var request = aSubject.QueryInterface(Components.interfaces.nsIRequest);
                  request.cancel(Components.results.NS_BINDING_ABORTED);

                }

      } 
   },

   QueryInterface: function(iid){
      if (!iid.equals(Components.interfaces.nsISupports) &&
          !iid.equals(Components.interfaces.nsIObserver))
        throw Components.results.NS_ERROR_NO_INTERFACE;

      return this;
   },

   readPostTextFromRequest : function(request) {
     var is = request.QueryInterface(Components.interfaces.nsIUploadChannel).uploadStream;
     if (is)
     {
       var ss = is.QueryInterface(Components.interfaces.nsISeekableStream);
       var prevOffset;
       if (ss)
       {
         prevOffset = ss.tell();
         ss.seek(Components.interfaces.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(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);

       return text;
     }
     else {
       dump("Failed to Query Interface for upload stream.\n");
     }
   }
   return null;
 },

 readFromStream : function(stream, charset, noClose)    
 {
    var sis = Components.classes["@mozilla.org/binaryinputstream;1"]
                     .getService(Components.interfaces.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;
  }
};

function openInTab(url, postData)
{
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  .getService(Components.interfaces.nsIWindowMediator);
  var recentWindow = wm.getMostRecentWindow("navigator:browser");
  if (recentWindow) 
  {
    // Use an existing browser window, open tab and "select" it
    recentWindow.gBrowser.selectedTab = recentWindow.gBrowser.addTab(url, null, null, postData);
  } 
}

function parseQuery() {
  var qry = this;
  var rex = /[?&]?([^=]+)(?:=([^&#]*))?/g;
  var qmatch, key;
  var paramValues = {};
  // parse querystring storing key/values in the ParamValues associative array
  while (qmatch = rex.exec(qry)) {
    key = decodeURIComponent(qmatch[1]);// get decoded key
    val = decodeURIComponent(qmatch[2]);// get decoded value

    paramValues[key] = val;
  }
  return paramValues;
}

function postDataFromString(dataString)
{
  // POST method requests must wrap the encoded text in a MIME
  // stream
  var stringStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
               .createInstance(Components.interfaces.nsIStringInputStream);
  if ("data" in stringStream) // Gecko 1.9 or newer
    stringStream.data = dataString;
  else // 1.8 or older
    stringStream.setData(dataString, dataString.length);

  var postData = Components.classes["@mozilla.org/network/mime-input-stream;1"].
           createInstance(Components.interfaces.nsIMIMEInputStream);
  postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
  postData.addContentLength = true;
  postData.setData(stringStream);

  return postData;
}

I'll update this to fill in the blanks in a bit.

edit: see http://forums.mozillazine.org/viewtopic.php?p=2772951#p2772951 for how to get the source window of a request.

Request cancellation code from http://zenit.senecac.on.ca/wiki/index.php/Support_For_OpenID.

see http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIRequest.idl for details on nsIRequest.

See http://forums.mozillazine.org/viewtopic.php?p=2404533#p2404533 and https://developer.mozilla.org/en/XUL/Method/addTab for the definition of addTab.

parseQuery comes from http://blog.strictly-software.com/2008/10/using-javascript-to-parse-querystring.html.

See https://developer.mozilla.org/en/Code_snippets/Post_data_to_window#Preprocessing_POST_data for how to process post data in a form suitable for addTab.

ReadPostFromText and ReadTextFromStream both come from firebug (though slightly modified)

Jonathan Fingland
Hey, this is looking great! Right now I'm trying to figure out how to tell whether the request came from my xul:browser element. I actually have a number of xul:browser elements at any given time and I'd like to intercept requests from all of them. Is it possible that I can just give each browser element the same class name: new_browser_element.setAttribute('class','mini_browser'); And then: if (DOMWindow.attr('class') == 'mini_browser') .... Obviously that is not correct, but would this class name be contained within the "DOMWindow" object?
makeee
You might need jQuery(DOMWindow).hasClass('mini_browser'). I suspect you'll need to use jQuery() to access the hasClass method
Jonathan Fingland
added post data passing and the minibrowser class check
Jonathan Fingland
Cool, really close to getting this. Few quick questions: shouldn't it be if(aTopic == 'http-on-modify-request')..? And your code right now will also load images embedded within the page into a new tab, since http-on-modify-request includes all requests, not just the page url. Any idea how to limit it to just the main page url? Thanks a lot!
makeee
ahh, sorry. that part was from your code and I didn't check it closely enough. fixed now. As to the images... that's a good point. In my extension I can use the url to limit what gets passed to a TracingListener, but I'll see what I can find out for your case.
Jonathan Fingland
what about waiting until http-on-examine-response? then check the headers for the mime type and, if appropriate, kill it then?
Jonathan Fingland
The problem with waiting until http-on-examine-response is that I want to cancel the request (so I want receive a response), then open the url in a tab instead. Been looking for a long time, but haven't been able to find any way to differentiate between the GET request for the page and the GET requests for the various images, css, etc that is on the page. Any ideas?
makeee
what about going old school and adding event listeners for click and submit? The click handler would check originalTarget and return false if the originalTarget (or an ancestor) was a link.
Jonathan Fingland
Thanks. My solution was to have jQuery replace all links hrefs with "mysite.com/go/"+original_url. Then my observer function checks to see if "mysite.com/go"; is in the requested url, and if so, opens it in a tab. I wanted to track outbound clicks anyway by redirecting to my site first, so this kills two birds with one stone.
makeee
excellent solution
Jonathan Fingland
I write a Jetpack Add-On and I want to get a callback when a browser window is opened/closed. I tried using nsIWindowWatcher, nsIWindowMediator, nsIObserverService with topic "DomWindowOpened" and "domwindowopened", the Jetpack observer service with the same topics and Jetpacks WindowTracker class, but NOTHING worked. Is there another method for this? My code (near the bottom in DBAIServer.start):https://web.student.tuwien.ac.at/~e0427417/hg/exin/public.cgi/file/tip/lib/main.js
panzi