Listing 4. The Javascript code for capturing an image is in the file
screengrab.js.

var picnikScreenGrab = {
  onLoad: function()
  {
    // Check if this firefox has 
    // canvas AND toDataURL
    var has_canvas = true;
    try
    {
      var canvas = document.createElementNS('http://www.w3.org/1999/xhtml','canvas');
      if( canvas == null )
        throw Exception;
      if( !("toDataURL" in canvas) )
        throw Exception;
    }
    catch(e)
    {
      has_canvas = false;
    }

    if( !has_canvas )
    {
      var e = document.getElementById("picnik-tool-menu");
      if(e)
        e.hidden=true;

      e = document.getElementById("picnik-button");
      if(e)
        e.hidden=true;

      e = document.getElementById("picnik-ctx-grab");
      if(e)
        e.hidden=true;
    }

  },

  // Grab the full webpage
  grabFull: function()
  {
    var h = 0;
    var w = 0;
    var d =  window.content.document;
    if( window.content.document.compatMode == "CSS1Compat" )
    {
      h = d.documentElement.scrollHeight;
      w = d.documentElement.scrollWidth;
    }
    else
    {
      h = d.body.scrollHeight;
      w = d.body.scrollWidth;
    }
    this.grab(window.content,0,0,w,h);
  },

  // Grab the visible part of the webpage
  grabVisible: function()
  {
    var x = window.content.scrollX;
    var y = window.content.scrollY; 
    var h = 0;
    var w = 0;
    var d =  window.content.document;
    if( window.content.document.compatMode == "CSS1Compat" )
    {
      h = d.documentElement.clientHeight;
      w = d.documentElement.clientWidth;
    }
    else
    {
      h = d.body.clientHeight;
      w = d.body.clientWidth;
    }
    
    this.grab(window.content,x,y,w,h);
  },

  grab: function(win, x, y, w, h)
  {
    const MAX_DIM = 2800;
    var canvasW = w;
    var canvasH = h;
    if( w>MAX_DIM || h>MAX_DIM)
    {
      // Scale the canvas size so that 
      // we fit within 2800x2800 Keep the 
      // aspect ratio so that the image 
      // isn't distorted
      if(w > h) 
      {
        canvasW = MAX_DIM;
        canvasH = canvasW*h/w;
      }
      else 
      {
        canvasH = MAX_DIM;
        canvasW = canvasH*w/h;
      }
    }

    // Make the canvas
    var canvas = document.createElementNS('http://www.w3.org/1999/xhtml','canvas');
    canvas.style.width = canvasW+"px";
    canvas.style.height = canvasH+"px";
    canvas.width = canvasW;
    canvas.height = canvasH;
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvasW, canvasH);
    ctx.save();
    ctx.scale(canvasW/w, canvasH/h);
    ctx.drawWindow(win, x, y, w, h, "rgb(255,255,255)");
    ctx.restore();
    
    // Save it to picnik
    data = canvas.toDataURL("image/png", "");
    this.saveDataURL(data, "image/png");
  },

  saveDataURL: function(url, mimeType)
  {
    const MULTI = "@mozilla.org/io/multiplex-input-stream;1";
    const STRINGIS = "@mozilla.org/io/string-input-stream;1";
    const IOSVC = "@mozilla.org/network/io-service;1"
    const nsIMultiplexInputStream = Components.interfaces.nsIMultiplexInputStream;
    const nsIStringInputStream = Components.interfaces.nsIStringInputStream;
    const nsIIOService = Components.interfaces.nsIIOService
    const BOUNDARY = "345823569845694578678";

    // Build the mime header
    var header = new String();
    header += "\r\n";
    header += "--" + BOUNDARY + "\r\n";
    header += "Content-disposition: form-data;name=\"_returntype\"\r\n\r\ntext\r\n";
    header += "--" + BOUNDARY + "\r\n";
    header += "Content-disposition: form-data;name=\"_apikey\"\r\n\r\n"+picnikCommon.APIKey+"\r\n";
    header += "--" + BOUNDARY + "\r\n";
    header += "Content-disposition: form-data;name=\"_file\";filename=\"snapshot.png\"\r\n";
    header += "Content-Type: "+mimeType+"\r\n";
    header += "\r\n";
    var header_stream = Components.classes[STRINGIS].createInstance(nsIStringInputStream);
    header_stream.setData(header, header.length);

    // create a data url from the canvas and then create a stream
    var io = Components.classes[IOSVC].getService(nsIIOService);
    var channel = io.newChannelFromURI( io.newURI(url, "UTF8", null) );
    var data_stream = channel.open();

    // Ending MIME boundary
    var end_stream = Components.classes[STRINGIS].createInstance(nsIStringInputStream);
    var bs = new String("\r\n--" + BOUNDARY + "--\r\n");
    end_stream.setData(bs, bs.length);

    // Stick 'em all together
    var mux_stream = Components.classes[MULTI].createInstance(nsIMultiplexInputStream);
    mux_stream.appendStream(header_stream);
    mux_stream.appendStream(data_stream);
    mux_stream.appendStream(end_stream);

    // Finally, POST it to picnik!
    var req = new XMLHttpRequest();
    req.open('POST', picnikCommon.baseURL+"service", true);
    req.onreadystatechange = function(){ picnikScreenGrab.requestState(req); };
    req.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
    req.setRequestHeader("Content-Length", mux_stream.available() );
    req.send(mux_stream);
  },

  // Called when XMLHttpRequest changes state as it does it's work
  requestState: function(req)
  {
    if( req.readyState == 4 )
    {
      // The request is now finished, so check the status code.
      if( req.status == 200 )
      {
        // The server said OK, and gave us a URL to load
        result = req.responseText;
        gBrowser.selectedTab = gBrowser.addTab(result);
      }
      else
      {
        alert('There was a problem with the request\nstatus=' + req.status);
      }
    }
  }
};

window.addEventListener("load", picnikScreenGrab.onLoad, false);
