// DMS2.0 :: Dust-Me Selectors
//***************************************************************************
// Copyright 2007 SitePoint Pty Ltd -- http://www.sitepoint.com/
// Written by brothercake -- http://www.brothercake.com/
// MIT License -- http://opensource.org/licenses/mit-license.php
//***************************************************************************

//dust-me selectors spider object
DustMeSelectors_spider = new Object();

//spider timer reference
DustMeSelectors_spider.spidertimer = null;


//when the chrome has loaded
window.addEventListener('load', function()
{
	//resize to content
	window.sizeToContent();

	//initialize the data saving options
	DustMeSelectors_tools.initialiseDataSavingOptions();

	//if there's no data in the hosts index, disable the view button
	if(DustMeSelectors_tools.getHostsIndex().length == 0)
	{
		DustMeSelectors_spider.setUIState('view', true);
	}

	//save a reference to the dialog browser element
	DustMeSelectors_spider.browser = document.getElementById('dms-spider-browser');

	//check the preferences for allowed mime-types
	//then set default values if they don't exist
	if(DustMeSelectors_preferences.getPreference('string', 'mimetypes.sitemap') == '')
	{
		DustMeSelectors_preferences.setPreference('string', 'mimetypes.sitemap',
			DustMeSelectors_preferences.mimetypes.sitemap)
	}
	if(DustMeSelectors_preferences.getPreference('string', 'mimetypes.pages') == '')
	{
		DustMeSelectors_preferences.setPreference('string', 'mimetypes.pages',
			DustMeSelectors_preferences.mimetypes.pages)
	}


}, false);



//menu command function: spider sitemap XML file
DustMeSelectors_spider.spiderSitemap = function()
{
	//save the sitemap URL
	//we have to use .value here rather than getAttribute('value')
	//because getAttribute always come back with a hard-coded default value
	//even if it's been changed by the user
	this.mapurl = document.getElementById('dms-spider-mapurl').value;

	//if the value is empty, do nothing
	if(this.mapurl == '') { return; }

	//if it doesn't have a protocol, add http
	//and write the modified value back to the text box, for reference
	if(!/^([a-z]+\:\/\/)/i.test(this.mapurl))
	{
		this.mapurl = 'http://' + this.mapurl;
		document.getElementById('dms-spider-mapurl').value = this.mapurl;
	}

	//initialize the progress indicator
	this.setUIState('progress-init');

	//show the spider statusbar
	this.setUIState('statusbar', false);

	//disable the URL group elements
	this.setUIState('urlgroup', true);

	//set the current url loading icon
	//this.setUIState('loadingurl', true);

	//now we want to establish the status of the file
	//by making an XHR request for it
	var request = new XMLHttpRequest();

	//when it completes
	request.onreadystatechange = function()
	{
		if(request.readyState == 4)
		{
			//do this on a try catch to handle errors
			//in case we get an NS_ERROR_NOT_AVAILABLE
			//from an address like http://xxx/
			try
			{
				//for successful requests we can continue
				if(/^(200|304)$/.test(request.status))
				{
					//get the response mime-type, and split to to remove any character encoding
					var mimetype = request.getResponseHeader('Content-Type').split(';')[0];

					//check the document content type against
					//the list of allowed sitemap types, and if they don't match
					if(DustMeSelectors_preferences.getPreference('string', 'mimetypes.sitemap').indexOf(mimetype) == -1)
					{
						//hide the spider statusbar
						DustMeSelectors_spider.setUIState('statusbar', true);

						//re-enable the URL group elements
						DustMeSelectors_spider.setUIState('urlgroup', false);

						//report the error
						window.openDialog('chrome://dustmeselectors/content/spidererror.xul?text='
							+ encodeURI(DustMeSelectors_tools.bundle.getString('error.notdocument')),
							'errdialog', 'chrome,centerscreen,modal,resizable=no');
					}

					//otherwise we're good
					else
					{
						//bind a domready handler to the browser to parse the sitemap
						DustMeSelectors_spider.browser.addEventListener('DOMContentLoaded',
							DustMeSelectors_spider.parseSitemap, false);

						//load the sitemap document into the browser
						DustMeSelectors_spider.browser.setAttribute('src', DustMeSelectors_spider.mapurl);
					}
				}

				//for errors
				else
				{
					//hide the spider statusbar
					DustMeSelectors_spider.setUIState('statusbar', true);

					//re-enable the URL group elements
					DustMeSelectors_spider.setUIState('urlgroup', false);

					//report the error
					//passing the text in the query string seems a bit hacky to me
					//i wanted to monitor the dialog load event and then append the data in the DOM
					//but that didn't work (load didn't fire), so instead, I do this
					//then in the dialog itself a load function splits out the data
					//and appends it to the description element directly
					window.openDialog('chrome://dustmeselectors/content/spidererror.xul?text='
						+ encodeURI(DustMeSelectors_tools.bundle.getString('error.xhr').replace('%1', request.statusText)),
						'errdialog', 'chrome,centerscreen,modal,resizable=no');
				}
			}
			catch(err)
			{
				//hide the spider statusbar
				DustMeSelectors_spider.setUIState('statusbar', true);

				//re-enable the URL group elements
				DustMeSelectors_spider.setUIState('urlgroup', false);

				//report the error
				window.openDialog('chrome://dustmeselectors/content/spidererror.xul?text='
					+ encodeURI(DustMeSelectors_tools.bundle.getString('error.notavailable')),
					'errdialog', 'chrome,centerscreen,modal,resizable=no');
			}
		}
	};

	//try make an aysnchronous request
	//and handle an error if it fails
	//we need to try catch this in case of malformed URLs
	try
	{
		request.open('GET', this.mapurl, true);
		request.send(null);
	}
	catch(err)
	{
		//hide the spider statusbar
		this.setUIState('statusbar', true);

		//re-enable the URL group elements
		this.setUIState('urlgroup', false);

		//report the error
		window.openDialog('chrome://dustmeselectors/content/spidererror.xul?text='
			+ encodeURI(DustMeSelectors_tools.bundle.getString('error.malformed')),
			'errdialog', 'chrome,centerscreen,modal,resizable=no');
	}
};



//parse sitemap
DustMeSelectors_spider.parseSitemap = function()
{
	//clear the current url loading icon
	DustMeSelectors_spider.setUIState('loadingurl', false);

	//get a reference to the browser document
	var doc = DustMeSelectors_spider.browser.contentDocument;

	//add sitemap address to history
	//we do this whether or not we find any links
	//i think that's best, in case the page is the problem and url is right
	//hence we want to remember attempts which might later be successful
	//(I couldn't find how to do this by searching mozdev or google
	// this code is straight from Firefox's source :D)
	var history = Components.classes["@mozilla.org/browser/global-history;2"]
		.getService(Components.interfaces.nsIBrowserHistory);
	var urifixer = Components.classes["@mozilla.org/docshell/urifixup;1"]
		.getService(Components.interfaces.nsIURIFixup);
	try 
	{
		var uri = urifixer.createFixupURI(doc.location.href, 0);
		history.markPageAsTyped(uri);
	}
	catch(err) {}
		
	//create an array for storing location URIs
	DustMeSelectors_spider.locations = [];

	//get the nofollow and ignorefragment preferences
	var nofollow = DustMeSelectors_preferences.getPreference('boolean', 'nofollow');
	var ignorefragment = DustMeSelectors_preferences.getPreference('boolean', 'ignorefragment');

	//look for loc elements in the target document
	//then iterate through the collection and extract their node values
	var locs = doc.getElementsByTagName('loc');
	for(var i=0; i<locs.length; i++)
	{
		if(locs[i].firstChild)
		{
			//copy the value, then if the ignorefragment preference is true
			//remove any fragment identifier from the URI
			var uri = locs[i].firstChild.nodeValue;
			if(ignorefragment) { uri = uri.split('#')[0]; }

			//add it to the locations array
			DustMeSelectors_spider.locations.push(uri);
		}
	}

	//if we don't have any locations,
	//look for a elements and extract their hrefs
	if(DustMeSelectors_spider.locations.length == 0)
	{
		locs = doc.getElementsByTagName('a');
		for(i=0; i<locs.length; i++)
		{
			//exclude anchors with no or empty href
			if(!locs[i].href || locs[i].href == '') { continue; }

			//if nofollow is true, exclude anchors that have
			//a rel attribute of "nofollow" or "external"
			if(nofollow &&
				(locs[i].getAttribute('rel') == 'nofollow'
					|| locs[i].getAttribute('rel') == 'external')) { continue; }

			//don't index special urls (about, javascript, mailto and callto)
			if(/^(about|javascript|mailto|callto)\:/i.test(locs[i].href)) { continue; }

			//copy the value, then if the ignorefragment preference is true
			//remove any fragment identifier from the URI
			var uri = locs[i].href;
			if(ignorefragment) { uri = uri.split('#')[0]; }

			//add it to the locations array
			DustMeSelectors_spider.locations.push(uri);
		}
	}

	//remove duplicate items from the locations array
	DustMeSelectors_spider.locations = DustMeSelectors_tools.arrayUnique(DustMeSelectors_spider.locations);

	//remove the domready listener and empty the browser
	DustMeSelectors_spider.browser.removeEventListener('DOMContentLoaded', DustMeSelectors_spider.parseSitemap, false);
	DustMeSelectors_spider.browser.removeAttribute('src');

	//if we still have no locations
	if(DustMeSelectors_spider.locations.length == 0)
	{
		//hide the spider statusbar
		DustMeSelectors_spider.setUIState('statusbar', true);

		//re-enable the URL group elements
		DustMeSelectors_spider.setUIState('urlgroup', false);

		//report the error
		window.openDialog('chrome://dustmeselectors/content/spidererror.xul?text='
			+ encodeURI(DustMeSelectors_tools.bundle.getString('error.nolinks')),
			'errdialog', 'chrome,centerscreen,modal,resizable=no');
	}

	//otherwise we have some to check
	else
	{
		//so update the progress indicator to be determined and at the beginning
		DustMeSelectors_spider.setUIState('progress-update', 0);

		//create the empty spider log
		//including the original sitemap url for csv output, a counter to track how many pages we've tested
		//and how many files were actually listed (ie, files is total,
		//whereas pages doesn't including unsupported files or pages that were skipped)
		//and a summary property to say whether this spider operation is incomplete or complete
		DustMeSelectors_spider.log = {
			'baseurl' : DustMeSelectors_spider.mapurl, 
			'files' : 0,
			'pages' : 0,
			'summary' : 'incomplete'
			};
		for(i=0; i<DustMeSelectors_spider.locations.length; i++)
		{
			DustMeSelectors_spider.log[DustMeSelectors_spider.locations[i]] = {
				'status' : null,
				'message' : null,
				};
		}

		//extract the host reference for selectors preference name from the browser's location
		DustMeSelectors_spider.prefname = DustMeSelectors_tools.getSelectorsDataPreferenceName(doc.location.href);

		//create a property to store the uri of sucesful requests for later reference
		//i was originally just reading back from the document uri
		//but that fails if the document gets redirected
		DustMeSelectors_spider.requesturi = null;

		//kick off by doing a find operation on the first URI in the locations array
		DustMeSelectors_spider.findRedundentSelectorsForPage(DustMeSelectors_spider.locations[0]);
	}
};




//find redundent selectors on a specified page
DustMeSelectors_spider.findRedundentSelectorsForPage = function(uri)
{
	//set the running property to true
	DustMeSelectors.running = true;

	//we need a property to count the number of remote stylesheets
	//from which we're still waiting for selectors data
	//(because the requests we'll be making for those stylesheets are asynchronous)
	DustMeSelectors.waiting = 0;

	//set the current url loading icon
	DustMeSelectors_spider.setUIState('loadingurl', true);

	//by making an XHR request for it
	var request = new XMLHttpRequest();

	//store the request uri as a property of this request
	//so that we can retrieve it from inside the readystatechange handler
	//without worrying about synchronisation conflicts
	request.uri = uri;
	
	//when it completes
	request.onreadystatechange = function()
	{
		if(request.readyState == 4)
		{
			//do this on a try catch to handle errors
			//in case we get an NS_ERROR_NOT_AVAILABLE
			//from an address like http://xxx/
			try
			{
				if(/^(200|304)$/.test(request.status))
				{
					//get the response mime-type, and split to to remove any character encoding
					var mimetype = request.getResponseHeader('Content-Type').split(';')[0];

					//check the document content type against
					//the list of allowed page types, and if they don't match
					if(DustMeSelectors_preferences.getPreference('string', 'mimetypes.pages').indexOf(mimetype) == -1)
					{
						//ignore this page and move on to the next one
						//passing the uri and error message for logic and logging
						DustMeSelectors_spider.ignore(
							request.uri,
							DustMeSelectors_tools.bundle.getString('spider.invalid').replace('%1', mimetype)
							);
					}

					//otherwise we're good
					else
					{
						//store the uri so that we can refer to it later
						//i was originally just reading back from the document uri
						//but that fails if the document gets redirected
						DustMeSelectors_spider.requesturi = request.uri;
						
						//update the currenturl field with the new page URL
						DustMeSelectors_spider.setUIState('currenturl', request.uri);
					
						//bind a domready handler to the browser to do the find operation
						DustMeSelectors_spider.browser.addEventListener('DOMContentLoaded',
							DustMeSelectors_spider.doFindOperation, false);
						
						//then load the page to kick that off
						DustMeSelectors_spider.browser.setAttribute('src', request.uri);
						
						//enable the skip button
						DustMeSelectors_spider.setUIState('skipbutton', false);
					}
				}

				//for any other status code
				else
				{
					//ignore this page and move on to the next one
					//padding the error message for logic and logging
					DustMeSelectors_spider.ignore(
						request.uri,
						DustMeSelectors_tools.bundle.getString('error.xhr').replace('%1', request.statusText)
						);
				}
			}
			catch(err)
			{
				//ignore this page and move on to the next one
				//padding the error message for logic and logging
				DustMeSelectors_spider.ignore(
					request.uri,
					DustMeSelectors_tools.bundle.getString('error.notavailable')
					);
			}
		}
	};

	//try make an aysnchronous request
	//and handle an error if it fails
	//we need to try catch this in case of malformed URLs
	//make a HEAD request so we don't transfer more data than we need wastefully
	try
	{
		request.open('HEAD', request.uri, true);
		request.send(null);
	}
	catch(err)
	{
		//ignore this page and move on to the next one
		//padding the error message for logic and logging
		DustMeSelectors_spider.ignore(
			request.uri,
			DustMeSelectors_tools.bundle.getString('error.malformed')
			);
	}
};


//do the find operation in response to domready event
DustMeSelectors_spider.doFindOperation = function()
{
	//clear the current url loading icon
	DustMeSelectors_spider.setUIState('loadingurl', false);

	//show the group of find status icons in the statusbar
	DustMeSelectors_spider.setUIState('statusicons', false);
	
	//if the currenturi doesn't match the content document location
	if(DustMeSelectors_spider.requesturi != DustMeSelectors_spider.browser.contentDocument.location.href)
	{
		//i think we need to re-index or delete and re-create both the locations and log data
		//so that the final uri is stored, with the original uri as the log message
		for(var i=0; i<DustMeSelectors_spider.locations.length; i++)
		{
			if(DustMeSelectors_spider.locations[i] == DustMeSelectors_spider.requesturi)
			{
				DustMeSelectors_spider.locations[i] = DustMeSelectors_spider.browser.contentDocument.location.href;
				delete DustMeSelectors_spider.log[DustMeSelectors_spider.requesturi];
				DustMeSelectors_spider.log[DustMeSelectors_spider.browser.contentDocument.location.href] = {
					'status' : null, 
					'message' : DustMeSelectors_tools.bundle
						.getString('spider.redirected').replace('%1', DustMeSelectors_spider.requesturi)
					};
				
				break;
			}
		}
	}
	//otherwise just nullify the property to reset it
	else
	{
		DustMeSelectors_spider.requesturi = null;
	}

	//update the currenturl field with the current page URL
	DustMeSelectors_spider.setUIState('currenturl', DustMeSelectors_spider.browser.contentDocument.location.href);

	//perform the find operation
	DustMeSelectors.getAllSelectors(DustMeSelectors_spider.prefname,
		DustMeSelectors_spider.browser.contentDocument);

	//if the spidering restart preference is true
	//bind mutation events to start again if the DOM changes mid-process
	if(DustMeSelectors_preferences.getPreference('boolean', 'restart'))
	{
		DustMeSelectors_spider.browser.contentDocument.addEventListener('DOMNodeInserted', function(e)
			{ DustMeSelectors_tools.handleContentMutation(e.relatedNode); }, false);
		DustMeSelectors_spider.browser.contentDocument.addEventListener('DOMNodeRemoved', function(e)
			{ DustMeSelectors_tools.handleContentMutation(e.relatedNode); }, false);
	}
};



//mid-spider save and continue operation
DustMeSelectors_spider.saveAndContinue = function(selectors, used)
{
	//disable the skip button
	this.setUIState('skipbutton', true);

	//update the progress indicator to show the current progress
	this.setUIState('progress-update', Math.ceil(100 * (this.log.files / this.locations.length)));

	//convert selectors and used objects to JSON and save to file
	//and add this host to the hostsindex, if it doesn't exist already
	DustMeSelectors_filesystem.dumpToFile(this.prefname + '.json', DustMeSelectors_tools.Object_toJSONString(selectors));
	DustMeSelectors_filesystem.dumpToFile(this.prefname.replace('selectors', 'used') + '.json', DustMeSelectors_tools.Object_toJSONString(used));
	DustMeSelectors_tools.updateHostsIndex(this.prefname, true);

	//update the hosts index array in the opener instance
	//which we'll need when we open its view dialog
	//but we do it here instead of at final output
	//so that if a spider operation is abandonded midway
	//it's still up to date with the data saved so far
	opener.DustMeSelectors_tools.hostsindex = opener.DustMeSelectors_tools.getHostsIndex();

	//add the checked status flag to this item in the log
	this.log[DustMeSelectors_spider.browser.contentDocument.location.href]['status'] = 'checked';

	//increase the files and pages count in the spider log
	this.log.files++;
	this.log.pages++;

	//save the log
	this.saveLog();

	//enable the view button if necessary
	this.setUIState('view', false);

	//now iterate through the locations array, and compare with log status data
	//to look for a file we haven't checked yet
	for(var i=0; i<this.locations.length; i++)
	{
		//if we find one then continue to do the next find operation, and we're done here
		if(this.log[this.locations[i]].status === null)
		{
			this.findRedundentSelectorsForPage(this.locations[i]);
			return;
		}
	}

	//if we're still going then we didn't find any more files
	//so proceed to final output
	this.finalOutput();
};



//save the spider log
DustMeSelectors_spider.saveLog = function()
{
	//convert files and pages values to strings
	//so that all the json data is the same datatype
	//which is simpler ... but that's post rationale
	//the real reason is that i didn't bother to
	//include a number->json method :D
	this.log.files = this.log.files.toString();
	this.log.pages = this.log.pages.toString();

	//convert log object to JSON and save to file
	DustMeSelectors_filesystem.dumpToFile(this.prefname.replace('selectors', 'log')
		+ '.json', DustMeSelectors_tools.Object_toJSONString(this.log));

	//convert files and pages values back to numbers
	this.log.files = parseInt(this.log.files, 10);
	this.log.pages = parseInt(this.log.pages, 10);
};



//final output from a spidering operation
DustMeSelectors_spider.finalOutput = function()
{
	//add the summary complete flag to log,
	this.log.summary = 'complete';

	//convert files and pages values to strings
	this.log.files = this.log.files.toString();
	this.log.pages = this.log.pages.toString();

	//convert log object to JSON and resave
	DustMeSelectors_filesystem.dumpToFile(this.prefname.replace('selectors', 'log')
		+ '.json', DustMeSelectors_tools.Object_toJSONString(this.log));

	//if the openafterspider preference is true, view saved selectors but don't focus the window
	if(DustMeSelectors_preferences.getPreference('boolean', 'openafterspider'))
	{
		this.viewSavedSelectors(false);
	}

	//send a stop command to reset the UI
	//and reset the running property to false
	this.stop();
};


//menu command function: view saved selectors
DustMeSelectors_spider.viewSavedSelectors = function(direct)
{
	//copy the preference name back to the parent instance
	opener.DustMeSelectors_browser.prefname = this.prefname;

	//open the view dialog from the parent instance, forcing the selectors tab
	opener.DustMeSelectors_browser.viewSavedSelectors(this.prefname, true, direct);
}





//menu command function: stop spider operation
DustMeSelectors_spider.stop = function()
{
	//set the dustme running property to false
	//so that the stop command cascades into the dustme script
	DustMeSelectors.running = false;

	//remove the domready listener and empty the browser
	this.browser.removeEventListener('DOMContentLoaded', DustMeSelectors_spider.doFindOperation, false);
	this.browser.removeAttribute('src');

	//re-enable the URL group elements
	this.setUIState('urlgroup', false);

	//wait a moment, then clear and hide the spider statusbar
	//we wait a moment simply so you get the chance
	//to see the progress meter get to 100%
	window.setTimeout(function()
	{
		DustMeSelectors_spider.updateStatusLabel(null, null, null);
		DustMeSelectors_spider.setUIState('statusbar', true);
		DustMeSelectors_spider.setUIState('loadingurl', false);

	}, 100);
};


//restart a spider operation on the same page in response to a mutation event
DustMeSelectors_spider.restart = function()
{
	//set the dustme running property to false
	//so that the stop command cascades into the dustme script
	DustMeSelectors.running = false;

	//wait to give the stop time to happen, then restart the find operation
	window.setTimeout(function()
	{
		//set the running property to true
		DustMeSelectors.running = true;

		//reset the waiting property
		DustMeSelectors.waiting = 0;

		//redo the find operation
		DustMeSelectors.getAllSelectors(
			DustMeSelectors_spider.prefname,
			DustMeSelectors_spider.browser.contentDocument
			);

	}, 300);
};


//ignore a page during spidering because of an error or unsupported mime-type
DustMeSelectors_spider.ignore = function(uri, reason)
{
	//update the currenturl field
	this.setUIState('currenturl', uri);

	//to ignore this page and move on to the next one
	//call skip with the error message as reason
	//the presence of the message tells skip what to do
	//and is also logged in the spidering log
	this.skip(reason, uri);
};


//menu command function: skip a page during a spider operation
DustMeSelectors_spider.skip = function(reason, uri)
{
	//disable the skip button
	this.setUIState('skipbutton', true);

	//set the dustme running property to false
	//so that the stop command cascades into the dustme script
	DustMeSelectors.running = false;

	//if the reason flag is not defined this is a manual skip
	//so get the uri from the content document location
	//and add the skipped status flag and the skipped message
	if(typeof reason == 'undefined')
	{
		var uri = DustMeSelectors_spider.browser.contentDocument.location.href;
		this.log[uri]['status'] = 'skipped';
		this.log[uri]['message'] = DustMeSelectors_tools.bundle.getString('spider.skipped');
	}
	//otherwise the reason flag is the text of the reason message
	//so use the uri argument that will also thence be present
	//and add the notchecked status flag and the reason as message
	else
	{
		this.log[uri]['status'] = 'notchecked';
		this.log[uri]['message'] = reason;
	}

	//increase the files count but not the page count
	this.log.files++;

	//save the log
	this.saveLog();

	//remove the domready listener and empty the browser
	this.browser.removeEventListener('DOMContentLoaded', DustMeSelectors_spider.doFindOperation, false);
	this.browser.removeAttribute('src');

	//update the progress indicator to show the current progress
	this.setUIState('progress-update', Math.ceil(100 * (this.log.files / this.locations.length)));

	//wait to give the stop time to happen, then move on to the next page
	window.setTimeout(function()
	{
		//now iterate through the locations array, and compare with log status data
		//to look for a file we haven't checked yet
		for(var i=0; i<DustMeSelectors_spider.locations.length; i++)
		{
			//if we find one then continue to do the next find operation, and we're done here
			if(DustMeSelectors_spider.log[DustMeSelectors_spider.locations[i]].status === null)
			{
				DustMeSelectors_spider.findRedundentSelectorsForPage(DustMeSelectors_spider.locations[i]);
				return;
			}
		}

		//if we're still going then we didn't find any more files
		//so proceed to final output
		DustMeSelectors_spider.finalOutput();

	}, 300);
};



//update the status label
DustMeSelectors_spider.updateStatusLabel = function(style, rules, sheets)
{
	//save a reference to the status label
	var statuslabel = document.getElementById('dms-spider-statuslabel');

	//if the rules and sheets arguments are strictly null,
	//remove their value and tooltip attributes and hide the label
	if(rules === null && sheets === null)
	{
		statuslabel.removeAttribute('value');
		statuslabel.removeAttribute('tooltiptext');
	}

	//otherwise if the values are not both strictly true apply the values
	else if(!(rules === true && sheets === true))
	{
		statuslabel.setAttribute('value', rules + '/' + sheets + '/' + (this.log.pages + 1));
		statuslabel.setAttribute('tooltiptext', DustMeSelectors_tools.bundle
			.getString('spider.summary')
			.replace('%1', rules)
			.replace('%P1', (rules == 1 ? '' : DustMeSelectors_tools.bundle.getString('view.plural')))
			.replace('%2', sheets)
			.replace('%P2', (sheets == 1 ? '' : DustMeSelectors_tools.bundle.getString('view.plural')))
			.replace('%3', this.log.pages + 1)
			.replace('%P3', (this.log.pages == 0 ? '' : DustMeSelectors_tools.bundle.getString('view.plural')))
			);
	}

	//if the values are boolean true, it means leave the value and tooltip as it is
};



//log to the dev console 
DustMeSelectors_spider.logToConsole = function(text)
{
	//do all this on a try catch, for now
	try
	{
		//get the console element
		var console = opener.content.document.getElementById('logdata');
		
		//compile the message
		var msg = text 
			+ ' [' + new Date().getHours() 
			+ ':' + new Date().getMinutes() 
			+ ':' + new Date().getSeconds() 
			+ '.' + new Date().getMilliseconds() + ']\n';
			
		//write back to console
		console.value += msg;
	}
	catch(err) {}
};



//set the UI state
DustMeSelectors_spider.setUIState = function(type, arg)
{
	switch(type)
	{
		case 'urlgroup' :

			document.getElementById('dms-spider-gobutton').setAttribute('disabled', arg);
			document.getElementById('dms-spider-stopbutton').setAttribute('disabled', !arg);
			if(arg === false)
			{
				DustMeSelectors_spider.setUIState('skipbutton', true);
			}

			break;

		case 'skipbutton' :

			document.getElementById('dms-spider-skipbutton').setAttribute('disabled', arg);

			break;

		case 'statusbar' :

			document.getElementById('dms-spider-statusbar').setAttribute('hidden', arg);
			if(arg === true)
			{
				DustMeSelectors_spider.setUIState('statusicons', true);
			}
			window.sizeToContent();

			break;

		case 'statusicons' :

			document.getElementById('dms-spider-statusicons').setAttribute('hidden', arg);

			break;

		case 'progress-init' :

			document.getElementById('dms-spider-progress').setAttribute('mode', 'undetermined');
			document.getElementById('dms-spider-progress').setAttribute('tooltiptext',
				DustMeSelectors_tools.bundle.getString('spider.processing'));
			document.getElementById('dms-spider-currenturl').setAttribute('value',
				DustMeSelectors_tools.bundle.getString('spider.processing'));

			break;

		case 'progress-update' :

			document.getElementById('dms-spider-progress').setAttribute('mode', 'determined');
			document.getElementById('dms-spider-progress').setAttribute('value', arg);
			document.getElementById('dms-spider-progress').setAttribute('tooltiptext',
				DustMeSelectors_tools.bundle.getString('spider.progress').replace('%1', arg));

			break;

		case 'currenturl' :

			var url = arg, domain = this.prefname.replace('selectors.', '').replace('#', ':');
			if(arg.indexOf(domain) != -1) { arg = arg.replace(/^([a-z]+\:\/\/[^\/]+)/, ''); }
			var status = DustMeSelectors_tools.bundle.getString('spider.status');
			if(arg.length > 35)
			{
				status = status.replace('%1', arg.substr(0, 35))
					.replace('%2', DustMeSelectors_tools.bundle.getString('spider.dots'));
			}
			else
			{
				status = status.replace('%1', arg).replace('%2', '');

			}
			document.getElementById('dms-spider-currenturl').setAttribute('value',
				status.replace('%3', (this.log.files + 1)).replace('%4', this.locations.length));
			document.getElementById('dms-spider-currenturl').setAttribute('tooltiptext',
				DustMeSelectors_tools.bundle.getString('spider.statustitle')
				.replace('%1', url).replace('%2', (this.log.files + 1)).replace('%3', this.locations.length));

			break;
			
		case 'loadingurl' : 

			document.getElementById('dms-spider-currenturl').setAttribute('style', 
				arg ? 'min-height:16px;background:url(chrome://dustmeselectors/content/loading.gif) no-repeat 100% 0;'
				: '');
							
			break;
			
		case 'view' :

			document.getElementById('dms-spider-view').setAttribute('disabled', arg);

			break;
	}
};