Firefox Add-on SDK code set that creates a context (right-click) menu item allowing the user to download both an image and it’s information to the OS’s current desktop.

This is specialized code for a small project, so would need some work to be universally capable.  It was made to complement my Adobe Illustrator script for creating image posters.
It only works on, and will probably break for the rest of the internet. However, this context menu will only appear when you are on a Flickr website.

You will need to know or learn how to create and install a script to make this work.

Main code, stored in {your addon dir}/lib/main.js

exports.main = function (options, callbacks) {
   // load the context menu script. (Is part of the Firefox SDK.)
   var contextMenu = require("context-menu");
   // reference and store the local directory "data"
   // element-getter.js is stored in this directory
   var data = require("self").data;

   var fileMgr = require("fileMgr-test.js");

      // name that appears in the right-click menu
      label: "Download image and info to desktop.",
      // only show this menu item when on Flickr website
      context: contextMenu.URLContext("*"),
      // show this menu item after the entire page is loaded.  
      // This may take a while at times, and may make the user wonder where the menu item is.
      contentScriptWhen : "ready",
      // load this script onto the page (stored in the {local}/data directory).
      contentScriptFile : [data.url('element-getter.js')],
      // when a message is sent (from the page to the menu item), run this
      // elementContent = a JSON object with image name, URL, author, and copyright data
      onMessage: function (elementContent) {
         // record some info to the console -- run this from the command line to see this data
         console.log( fileMgr.joinQuoted(elementContent) );
         console.log("title: " + elementContent[4]);
         // save the image info
         fileMgr.saveText( elementContent );
         // save the image
         fileMgr.downloadImage(elementContent[3], elementContent[4]);


Context menu functions, stored in {your addon dir}/lib/fileMgr-test.js

'use strict';

// super powerful Component script.  Allows access to everything a browser can access.
// As noted by Firefox, this is experimental, and may change or be removed in the future.
var {Cc, Ci, Cm, Cr, Cu} = require("chrome");

// joins an array with a single-quote and comma
var joinQuoted = function(arr){
         // This will wrap each element of the array with quotes
         return "'" + elem + "'";
      }).join(","); // This puts a comma in between every element

// make joinQuoted a public function that other scripts can use
exports.joinQuoted = joinQuoted;

// save (create, else append) textJSON to the (hard-coded) file "flickrFileInfo.txt"
// This function is public to other scripts.
// NOTE:  The text in the file is an array of arrays, but it will not have a closing bracket.  This will have to be added manually.
exports.saveText = function(textJSON) {

   // load file stuff

   // join textJSON array with quotes
   var joinedText = ",[" + joinQuoted(textJSON) + "]";
   console.log("saveText.text = " + joinedText);

   // create a new file reference to the OS's current desktop directory
   var file = Cc[";1"].
           get("Desk", Ci.nsIFile);
   // reference a specific file for appending data

   console.log("intended file.path:");

   // create the file if it doesn't exist
   if ( file.exists() == false ) {
      console.log( "Creating file: " + file.path );
      file.create( Ci.nsIFile.NORMAL_FILE_TYPE, 420 );

      // fix joinedText for the first entry
      // replace initial comma with opening bracket
      // NOTE:  The file will not have a closing bracket.  This will have to be added manually.
      joinedText = "[" + joinedText.substring(1,joinedText.length);

   // create an output stream
   var outputStream = Cc[";1"]
      .createInstance( Ci.nsIFileOutputStream );

   // flags found here:

   // initalize/start the output stream.
   // write only, create if non-existent (redundant), append (instead of overwriting)
   outputStream.init( file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_APPEND, 420, 0 );
   // write joinedText to file
   var result = outputStream.write( joinedText, joinedText.length );
   // close the stream

// save (create or overwrite) an image from URI to file on the OS's current desktop
// This function is public to other scripts.
exports.downloadImage = function(aURLToDownload, aSaveToFile) {

   // import file stuff

   // create a new web browser instance
   // this is not visible to the user
   var persist = Cc[";1"]

   // get or create a file named aSaveToFile on the OS's current desktop 
   var file = FileUtils.getFile("Desk", [aSaveToFile + ".jpeg"]);
   console.log("image output file = " + file.path);

   // create an URI from the string aURLToDownload
   var obj_URI = Cc[";1"]
              .newURI(aURLToDownload, null, null);

   // set up the listeners for the hidden web browser
   persist.progressListener = {

      onProgressChange: function(
         // display download progress
         // NOTE: DOM element "progress_element" has to exist
         var percentComplete = (aCurTotalProgress/aMaxTotalProgress)*100;
         var ele = document.getElementById("progress_element");
         ele.innerHTML = percentComplete +"%";
      onStateChange: function(
         // do something

   // begin the download
   persist.saveURI(obj_URI, null, null, null, "", file);

Code added to the page content, stored in {your addon dir}/data/element-getter.js

var newurl = "";

// Search the DOM, and find various info about the image.
// This code is intened for, and will probably fail on other sites.

// This javascript file is _not_ connected to the context (right-click) menu.  They are in two separate code areas.
// The only way this file can communicate with the context menu is "self.postMessage(info)", 
// where "info" is a JSON object.
self.on('click', function (node, data) {
   console.log("Menu item clicked.");

   // store various DOM references for easy changes
   var tag = "a";
   var attr = "rel";
   var licenseVal = "license cc:license";
   var titleTag = "title_div";
   var imgTag = "liquid-photo";
   var imgAttr = "src";

   // store an array of tag elements
   var elements = document.getElementsByTagName(tag);

   // URL 1:
   // URL 2:
   //{photo rights owner}/{photo id}{/}{possible stuff}

   // process the array of tag elements
   elementLoop:  for (var i = 0; i < elements.length; i++) 
      // only save files with CC licenses:
      if(elements[i].getAttribute(attr) == licenseVal)
         // create an array for storing results
         var outv = [];

         // store URL
         // store author ID
         // This is not the author's real name, which should probably be stored as well.
         outv.push(document.URL.replace(/.*\/photos\/(.*)\/\d*\/.*/, '$1'));
         // store license type (CC type)
         outv.push("CC " + elements[i].getAttribute("href").replace(/.*licenses.(.*).deed.*/, '$1').replace(/[-|\/]/g," ").toUpperCase());
         // store image
         // the original can be accessed by changing "z" to "o", and there are other options, 
         // but Flickr will not let you download the original without going through their interface.
         outv.push(document.getElementById(imgTag).src); //.replace("_z","_o"));
         // store title

         console.log("  title: " + outv[4]);

         // send the data to the context (right-click) menu

         // break after first successful element, since any success means the whole page matches.
         break elementLoop;