Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");

(function(a) {
	var Cc = Components.classes;
	var Ci = Components.interfaces;
	var d = document; // XUL dialog document
	var w = a.content; // content window
	var cd = w.document; // content document
	var album; // album information object
	var max_progress_row = 40; // width of progress map
	var in_last_progress_row = 40; // number of images in last row
	var last_progress_row; // DOM object of last progress row box
	var max_in_progress = 8; // maximum number of active images
	var in_progress = 0; // number of active images
	var num_done = 0; // number of completed images
	var output_directory; // nsIFile with the output directory
	function _(array_like) {
		return Array.prototype.slice.call(array_like);
	}
	function log(string) {
		d.querySelector('#facepaste-status').value = string;
	}
	function progress(i, progress) {
		album[i].progress = progress;
		d.querySelectorAll('#facepaste-progress-box .facepaste-progress')[i].
			className = 'facepaste-progress facepaste-progress-' + progress;
	}
	function ajax(url, binary, type, callback, callback_data) {
		var r = new XMLHttpRequest;
		r.onload = function() {
			callback(r.response, r, callback_data);
		};
		r.open('GET', url);
		/*
			this is disgusting and I'd prefer to just use a responseType of
			blob or arraybuffer, but I can't find anything in the Firefox addon
			API to turn these into a stream that can be used to write to a file.
		*/
		if (binary)
			r.overrideMimeType('text/plain; charset=x-user-defined');
		r.responseType = type;
		r.send(null);
	}
	function choose_directory() {
		var picker = Cc["@mozilla.org/filepicker;1"].
			createInstance(Ci.nsIFilePicker);
		picker.init(window, 'Choose a directory to download photos to',
			Ci.nsIFilePicker.modeGetFolder);
		var retval = picker.show();
		if (retval == Ci.nsIFilePicker.returnOK)
			return picker.file;
	}
	function write_file(name, data) {
		var file = output_directory.clone();
		file.append(name);
		var istream = Cc["@mozilla.org/io/string-input-stream;1"].
			createInstance(Ci.nsIStringInputStream);
		var ostream = FileUtils.openSafeFileOutputStream(file);
		istream.data = data;
		NetUtil.asyncCopy(istream, ostream);
	}
	function start() {
		setInterval(function() {
			album.forEach(function(p, pi) {
				if (p.progress == 'waiting' && in_progress < max_in_progress) {
					progress(pi, 'preparing');
					// ajax(p.pageurl, false, 'document', handle_page, pi);
					ajax(p.pageurl, false, '', handle_page, pi);
					in_progress++;
				}
			});
		}, 1000);
	}
	function handle_page(res, req, pi) {
		progress(pi, 'downloading');
		album[pi].imageurl = res.match(
			/<a class="fbPhotosPhotoActionsItem" href="([^"]+)" rel="ignore">/
		)[1];
		ajax(album[pi].imageurl, true, '', handle_image, pi);
	}
	function handle_image(res, req, pi) {
		progress(pi, 'complete');
		write_file(album[pi].imageurl.match(/([^\/]+)\?/)[1], res);
		in_progress--;
		num_done++;
		log('Downloaded ' + num_done + ' of ' + album.length + ' images');
	}
	d.querySelector('#facepaste-title').setAttribute('title',
		cd.querySelector('title').textContent);
	album = _(cd.querySelectorAll('a.uiMediaThumb:not(.uiMediaThumbAlb)')).
		map(function(l) {
		if (in_last_progress_row == max_progress_row) {
			last_progress_row = d.createElement('box');
			last_progress_row.setAttribute('orient', 'horizontal');
			d.querySelector('#facepaste-progress-box').
				appendChild(last_progress_row);
			in_last_progress_row = 0;
		}
		var progress_dot = document.createElement('box');
		progress_dot.className =
			'facepaste-progress facepaste-progress-waiting';
		last_progress_row.appendChild(progress_dot);
		in_last_progress_row++;
		return { pageurl: l.href, progress: 'waiting' };
	});
	output_directory = choose_directory();
	if (output_directory)
		start();
	else
		window.close();
})(arguments[0]);

@@ -761,16 +761,60 @@
 rectory%0A
+%09var poll_interval; // setInterval timer ID%0A
 %09functio
@@ -2362,16 +2362,32 @@
 t() %7B%0A%09%09
+poll_interval = 
 setInter
@@ -2403,16 +2403,47 @@
 ion() %7B%0A
+%09%09%09var done_in_this_round = 0;%0A
 %09%09%09album
@@ -2709,13 +2709,160 @@
 %09%09%09%7D
-%0A%09%09%09%7D
+ else if (p.progress == 'complete') %7B%0A%09%09%09%09%09done_in_this_round++;%0A%09%09%09%09%7D%0A%09%09%09%7D);%0A%09%09%09if (done_in_this_round == album.length)%0A%09%09%09%09clearInterval(poll_interval
 );%0A%09

stop the interval timer when all downloads are complete (fixes #6)