home *** CD-ROM | disk | FTP | other *** search
Text File | 2005-07-29 | 43.9 KB | 1,410 lines |
- // This file contains what could essentially be called the "Datacard API"
- // Mostly written by Chris Campbell and Justin Louie
-
- const nsIAutoFillService = Components.interfaces.nsIAutoFillService;
-
-
- var datacardUtils = {
-
- // Set true to enable debug output
- DEBUG : false,
-
- // Constants
- DATACARDS_FOLDER : 'datacards',
- DATACARD_FILE_EXT : '.dat',
- TEMP_DATACARD_FILENAME : '~~temp~~',
- SITELIST_FILE : 'datacard-sites.txt',
- SITELIST_PREFNAME : 'datacard.sites',
- IGNORELIST_PREFNAME : 'datacard.sites.ignorechanges',
- DEFAULT_DATACARD_PREFNAME : 'datacard.default',
- SPECIAL_CHARS_REGEXP : /\$|,|@|#|~|`|\%|\*|\^|\&|\(|\)|\+|\=|\[|\-|\_|\]|\[|\}|\{|\;|\:|\'|\"|\<|\>|\?|\||\\|\!|\$|\.|\s/g,
- FIELD_HIGHLIGHT_COLOR : '#ffff66',
- RULE_SEPARATOR : '=',
- FIELD_DELIMITER : '|',
-
- // Globals
- profileDir : null,
- ioService : null,
- prefService : null,
- afService : null,
- observerService : null,
- submitObserver : null,
-
-
- Init : function() {
- this.debug('Init()');
-
- // Get the user's profile directory
- if (!this.profileDir) {
- this.profileDir =
- Components.classes["@mozilla.org/file/directory_service;1"]
- .getService(Components.interfaces.nsIProperties)
- .get("ProfD", Components.interfaces.nsILocalFile);
- }
- this.debug(' profileDir: '+this.profileDir.path);
-
- // IO Service
- if (!this.ioService) {
- this.ioService =
- Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- }
-
- // Pref Service
- if (!this.prefService) {
- this.prefService =
- Components.classes["@mozilla.org/preferences-service;1"]
- .getService(Components.interfaces.nsIPrefBranch);
- }
-
- // Autofill service
- if (!this.afService) {
- this.afService =
- Components.classes["@mozilla.org/autofillService;1"]
- .getService(nsIAutoFillService);
- }
-
- // Observer service
- if (!this.observerService) {
- this.observerService =
- Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- }
- },
-
-
- InitSubmitObserver : function() {
- // Form submission observer
- if (!this.submitObserver) {
- // TODO: We may want to do something here to make sure this observer
- // only gets added *once* per application, *not* once per browser
- // window, as now happens
- this.submitObserver = new datacardSubmitObserver();
- this.observerService.addObserver(this.submitObserver, "formsubmit", true);
- }
- },
-
-
- GetDatacardsFolder : function() {
- this.debug('GetDatacardsFolder()');
- // Create file descriptor
- var folder = this.profileDir.clone();
- folder.append(this.DATACARDS_FOLDER);
- return folder; // returns nsILocalFile
- },
-
-
- EnsureDatacardsFolderExists : function() {
- this.debug('EnsureDatacardsFolderExists()');
- var folder = this.GetDatacardsFolder();
- if (!folder.exists()) {
- this.debug(' creating folder: '+this.DATACARDS_FOLDER);
- folder.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0);
- }
- return folder; // returns nsILocalFile
- },
-
-
- GetSiteListFile : function() {
- this.debug('GetSiteListFile()');
- // Create file descriptor
- var file = this.profileDir.clone();
- file.append(this.SITELIST_FILE);
- return file; // returns nsILocalFile
- },
-
-
- EnsureSiteListFileExists : function() {
- this.debug('EnsureSiteListFileExists()');
- var file = this.GetSiteListFile();
- if (!file.exists()) {
- this.EnsureDatacardsFolderExists();
- this.debug(' creating file: '+this.SITELIST_FILE);
- file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0);
- }
- return file; // returns nsILocalFile
- },
-
-
- GetSiteList : function() {
- // As with SetSiteList(), this should probably use a file
- // rather than a pref, but I was having probs getting it to work...
- var siteList = '';
- if (this.prefService.getPrefType(this.SITELIST_PREFNAME)) {
- siteList = this.prefService.getCharPref(this.SITELIST_PREFNAME);
- }
- this.debug('GetSiteList(): '+siteList);
- return siteList;
- },
-
-
- SetSiteList : function(siteList) {
- // As with GetSiteList(), this should probably use a file
- // rather than a pref, but I was having probs getting it to work...
- this.debug('SetSiteList(...)');
- this.prefService.setCharPref(this.SITELIST_PREFNAME, siteList);
- },
-
-
- GetSiteIgnoreChangesList : function() {
- var ignoreList = '';
- if (this.prefService.getPrefType(this.IGNORELIST_PREFNAME)) {
- ignoreList = this.prefService.getCharPref(this.IGNORELIST_PREFNAME);
- }
- this.debug('GetSiteIgnoreChangesList(): '+ignoreList);
- return ignoreList;
- },
-
-
- SetSiteIgnoreChangesList : function(ignoreList) {
- this.debug('SetSiteIgnoreChangesList(...)');
- this.prefService.setCharPref(this.IGNORELIST_PREFNAME, ignoreList);
- },
-
-
- FindDatacardForURI : function(uri) {
- this.debug('FindDatacardForURI('+uri.spec+')');
- var siteList = this.GetSiteList();
- var sites = siteList.split(',');
- for (var i = 0; i < sites.length; i++) {
- var arr = sites[i].split(':');
- if (arr[0] == uri.asciiHost) {
- this.debug(' -- datacard for this site is: '+arr[1]);
- if (!arr[1].length) return undefined;
- return arr[1];
- }
- }
- this.debug(' -- using default datacard');
- return this.GetDefaultDatacard();
- },
-
-
- GetDefaultDatacard : function() {
- this.debug('GetDefaultDatacard()');
- if (this.prefService.getPrefType(this.DEFAULT_DATACARD_PREFNAME)) {
- return this.prefService.getCharPref(this.DEFAULT_DATACARD_PREFNAME);
- }
- return null;
- },
-
-
- SetDefaultDatacard : function(datacard) {
- this.debug('SetDefaultDatacard('+datacard+')');
- this.prefService.setCharPref(this.DEFAULT_DATACARD_PREFNAME,datacard);
- },
-
-
- // Establish a host ==> datacard association
- SetDatacardForHost : function(host, datacard) {
- this.debug('SetDatacardForHost(host:'+host+', datacard:'+datacard+')');
- var siteList = this.GetSiteList();
- var sites;
- if (siteList.length)
- sites = siteList.split(',');
- else
- sites = new Array();
- var found = false;
- for (var i = 0; i < sites.length; i++) {
- var arr = sites[i].split(':');
- if (arr[0] == host) {
- found = true;
- // This site is already listed, so just update it
- sites[i] = arr[0]+':'+datacard;
- break;
- }
- }
- if (!found) {
- sites[sites.length] = host+':'+datacard;
- }
- this.SetSiteList(sites.join(','));
- },
-
-
- // Returns an array of the extant datacards
- GetDatacardList : function() {
- this.debug('GetDatacardList()');
- var list = new Array();
-
- // If there's no datacards folder, then there are no datacards
- var folder = this.GetDatacardsFolder();
- if (!folder.exists()) return list;
-
- // If the datacards folder contains ANY files at all...
- var entries = folder.directoryEntries;
- while (entries.hasMoreElements()) {
- var entry = entries.getNext();
- entry.QueryInterface(Components.interfaces.nsILocalFile);
- var fileName = entry.leafName;
- if (fileName.substring(fileName.length-4)=='.dat') {
- list[list.length] = fileName.substring(0,fileName.length-4);
- }
- }
- return list;
- },
-
-
- // Adds the specified site to the 'ignore changes' list
- IgnoreChangesToSite : function(site) {
- var host = this.ExtractHostname(site);
- this.debug('IgnoreChangesToSite('+host+')');
- var ignoreList = this.GetSiteIgnoreChangesList().split(',');
- var found = false;
- for (var i = 0; i < ignoreList.length; i++) {
- if (ignoreList[i] == host) {
- found = true;
- break;
- }
- }
- if (!found) {
- ignoreList[ignoreList.length] = host;
- this.SetSiteIgnoreChangesList(ignoreList.join(','));
- }
- },
-
-
- IsSiteOnIgnoreChangesList : function(site) {
- var host = this.ExtractHostname(site);
- var ignoreList = this.GetSiteIgnoreChangesList().split(',');
- var found = false;
- for (var i = 0; i < ignoreList.length; i++) {
- if (ignoreList[i] == host) {
- found = true;
- break;
- }
- }
- this.debug('IsSiteOnIgnoreChangesList('+host+'): '+((found)?'Yes':'No'));
- return found;
- },
-
-
- // Adds the specified site to the blacklist
- BlacklistSite : function(site) {
- var host = this.ExtractHostname(site);
- this.debug('BlacklistSite('+host+')');
- this.SetDatacardForHost(host, '');
- },
-
-
- IsSiteBlacklisted : function(site) {
- var host = this.ExtractHostname(site);
- var blacklist = this.GetBlacklistedSites();
- var found = false;
- for (var i = 0; i < blacklist.length; i++) {
- if (blacklist[i] == host) {
- found = true;
- break;
- }
- }
- this.debug('IsSiteBlacklisted('+host+'): '+((found)?'Yes':'No'));
- return found;
- },
-
-
- // Returns an array of the blacklisted host names
- GetBlacklistedSites : function() {
- this.debug('GetBlacklistedSites()');
- // Blacklisted sites are those sites which have no datacard name
- // associated with them in the site list
- var siteList = this.GetSiteList();
- var sites = siteList.split(',');
- var blacklist = new Array();
- for (var i = 0; i < sites.length; i++) {
- var arr = sites[i].split(':');
- if (!arr[1] || !arr[1].replace(/\s+$/).length) {
- this.debug(' - blacklist['+blacklist.length+']: '+arr[0]);
- blacklist[blacklist.length] = arr[0];
- } else {
- this.debug(' (excluding '+arr[0]+')');
- }
- }
- return blacklist;
- },
-
-
- // Pass in either a URI object or a hostname string, and
- // you will get back just the hostname string.
- ExtractHostname : function(site) {
- var uri = null;
- var host;
- try {
- uri = site.QueryInterface(Components.interfaces.nsIURI);
- host = uri.asciiHost;
- } catch (ex) {
- // Apparently 'site' wasn't a URI object, so assume it was
- // just a hostname
- host = site;
- }
- this.debug('ExtractHostname(...): '+host);
- return host;
- },
-
-
- // Removes a site (blacklisted or not) from the site list
- UnlistSite : function(site) {
- var host = this.ExtractHostname(site);
- this.debug('UnlistSite('+host+')');
- var siteList = this.GetSiteList();
- var sites = siteList.split(',');
- var newSites = new Array();
- for (var i = 0; i < sites.length; i++) {
- var arr = sites[i].split(':');
- if (arr[0] != host)
- newSites[newSites.length] = sites[i];
- }
- this.SetSiteList(newSites.join(','));
- },
-
-
- GetDatacardFileByName : function(name) {
- this.debug('GetDatacardFileByName('+name+')');
- var fileName = name + this.DATACARD_FILE_EXT;
- var file = this.GetDatacardsFolder();
- file.append(fileName);
- return file;
- },
-
-
- GenerateFilenamePrefixForDatacard : function(label) {
- var name = label.replace(this.SPECIAL_CHARS_REGEXP, '');
- this.debug('GenerateFilenamePrefixForDatacard('+label+'): '+name);
- return name;
- },
-
-
- CreateDatacard : function(name) {
- this.debug('CreateDatacard('+name+')');
- this.CreateDatacardTemp(name);
- if (!this.GetDefaultDatacard())
- this.SetDefaultDatacard(name);
- },
-
-
- CreateDatacardTemp : function(name) {
- this.debug('CreateDatacardTemp('+name+')');
- this.EnsureDatacardsFolderExists();
- var datacardFile = this.GetDatacardFileByName(name);
- if (!datacardFile.exists()) {
- this.debug(' creating datacard file: '+datacardFile.path);
- datacardFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0);
- }
- this.afService.SetDatacardFieldByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_PROPERTY,
- 'WhenToFill',
- 'displayprompt');
- this.afService.SetDatacardFieldByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_PROPERTY,
- 'Label',
- name);
-
- // Check a pref to see whether datacards should be password
- // protected by default
- var isEncrypted = false;
- if (this.prefService.getPrefType('datacard.passwordprotect') &&
- this.prefService.getBoolPref('datacard.passwordprotect'))
- {
- isEncrypted = true;
- this.debug(' ENCRYPTED');
- } else {
- this.debug(' UNencrypted');
- }
- this.afService.SetDatacardFieldByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_PROPERTY,
- 'Encrypted',
- (isEncrypted) ? '1' : '0');
- },
-
-
- // This is a wrapper function on the nsAutoFillService function
- // of the same name, specifically for the purpose of doing
- // country name substitution when necessary
- FillDatacardFieldsFromHTML : function(datacardFile,
- host,
- fieldNames,
- valuesArray,
- doSave)
- {
- this.debug('FillDatacardFieldsFromHTML(...)');
- var dict = this.afService
- .FillDatacardFieldsFromHTML(datacardFile,
- host,
- fieldNames,
- valuesArray,
- doSave);
- // Check if we need to do any country name substitution
- var keys = dict.getKeys({});
- for (var k in keys) {
- var key = keys[k];
- if (key == 'country' || key == 'alt_country') {
- var dubiousCountryObj = dict.getValue(key);
- dubiousCountryObj.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
- var dubiousCountry = dubiousCountryObj.toString();
- var matchCountry = findClosestCountryMatch(dubiousCountry);
- if (dubiousCountry != matchCountry) {
- // Set the dictionary value straight
- this.debug(' - matchCountry: '+matchCountry);
- dubiousCountryObj.setDataWithLength(matchCountry.length, matchCountry);
- if (doSave) {
- // Set the datacard value straight
- this.afService
- .SetDatacardFieldByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_REGULAR,
- key,
- matchCountry);
- }
- }
- }
- }
- return dict;
- },
-
-
- SetDatacardValues : function(datacard, uri, rippedValues) {
- this.debug('SetDatacardValues('+datacard+')');
- var datacardFile = this.GetDatacardFileByName(datacard);
- var values = this.ToPrefLocalizedStringArray(rippedValues.values);
- this.FillDatacardFieldsFromHTML(
- datacardFile,
- uri.asciiHost,
- rippedValues.fields.join(this.FIELD_DELIMITER),
- values,
- true); // save to file
- },
-
-
- DeleteDatacard : function(name) {
- this.debug('DeleteDatacard('+name+')');
- var siteList = this.GetSiteList();
- var sites = siteList.split(',');
- for (var i = 0; i < sites.length; i++) {
- var arr = sites[i].split(':');
- if (arr[1] && arr[1]==name) {
- this.UnlistSite(arr[0]);
- }
- }
- var file = this.GetDatacardFileByName(name);
- file.remove(false);
- if (name == this.GetDefaultDatacard()) {
- // Pick a new default
- var list = this.GetDatacardList();
- var newDefault = (list.length) ? list[0] : '';
- this.SetDefaultDatacard(newDefault);
- }
- },
-
-
- DeleteAllDatacards : function(onlyDoEncrypted) {
- this.debug('DeleteAllDatacards('+(onlyDoEncrypted)?'only encrypted)':')');
- var list = this.GetDatacardList();
- for (var i = 0; i < list.length; i++) {
- if (onlyDoEncrypted) {
- var file = this.GetDatacardFileByName(list[i]);
- file.QueryInterface(Components.interfaces.nsILocalFile);
- var props =
- this.afService.GetDatacardFieldsByType(
- file,
- nsIAutoFillService.FIELDTYPE_PROPERTY);
- var isEncrypted = props.getValue('Encrypted');
- isEncrypted.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
- isEncrypted = isEncrypted.toString();
- // If this datacard isn't encrypted, skip it
- if (isEncrypted != '1') continue;
- }
- this.DeleteDatacard(list[i]);
- }
- },
-
-
- DoAutoFill : function(uri, datacard, fieldValues, submit) {
- if (!uri)
- uri = gBrowser.currentURI;
- var defaultDatacard = this.FindDatacardForURI(uri);
- if (!datacard)
- datacard = defaultDatacard;
- if (!fieldValues)
- fieldValues = this.RipFields();
-
- this.debug('DoAutoFill('+uri.host+', '+datacard+')');
-
- // Since we're filling with this datacard, make sure it's the
- // default for this site from now on
- if (datacard != defaultDatacard)
- this.SetDatacardForHost(uri.host, datacard);
-
- var datacardFile = this.GetDatacardFileByName(datacard);
- fieldValues.preModifiedValues = this.ToPrefLocalizedStringArray(fieldValues.values);
- var htmlValues = this.InitPrefLocalizedStringArray(fieldValues.values.length);
- this.afService.FillHTMLFieldsFromDatacard(
- datacardFile,
- uri.asciiHost,
- fieldValues.fields.join(this.FIELD_DELIMITER),
- htmlValues);
- this.debug(' -> got these values to fill with:');
- this.debugPLStringArray(htmlValues);
- fieldValues.htmlValues = htmlValues;
-
- // Check if Trident
- var hpDoc = getTridentDocument();
- if (hpDoc) {
- try {
- this.DoAutoFillTrident(hpDoc, fieldValues);
- } catch (ex) {
- this.debug(' ***** Error autofilling on Trident!!!');
- }
- } else {
- this.DoAutoFillGecko(gBrowser.contentDocument, fieldValues);
- }
- if (submit) {
- this.debug(' - doing submit');
- if (hpDoc) {
- try {
- this.DoSubmitTrident(hpDoc, fieldValues);
- } catch (ex) {
- this.debug(' ***** Error autosubmitting on Trident!!!');
- }
- } else {
- this.DoSubmitGecko(gBrowser.contentDocument, fieldValues);
- }
- }
- },
-
-
- DoAutoFillTrident : function(htmlPluginDoc, fieldValues) {
- this.debug('DoAutoFillTrident()');
- var mangledString = fieldValues.fields.join(this.FIELD_DELIMITER);
- var e = fieldValues.htmlValues.enumerate();
- var vals = new Array();
- while (e.hasMoreElements())
- {
- vals[vals.length] = escape(e.getNext()
- .QueryInterface(Components.interfaces.nsIPrefLocalizedString)
- .toString());
- }
- mangledString += this.RULE_SEPARATOR + vals.join(this.FIELD_DELIMITER);
- htmlPluginDoc.FillFieldsTrident(mangledString);
- },
-
-
- // Actually fill gecko fields!!!
- DoAutoFillGecko : function(doc, fieldValues) {
- if (!doc) return;
- this.debug('DoAutoFillGecko()');
-
- // get list of input elements
- var fieldList = this.EnumerateInputFieldsGecko(doc);
-
- // for each field to use to fill with
- for (var i = 0; i < fieldValues.fields.length; i++) {
- var fieldName = fieldValues.fields[i];
- this.debug('* testing field: '+fieldName);
- // iterate through the input elements in the document
- for (var j = 0; j < fieldList.length; j++) {
- //this.debug('** matching against: '+fieldList[j].getAttribute('name'));
- if (fieldList[j].getAttribute('name') == fieldName) {
- var value =
- fieldValues.htmlValues.queryElementAt(
- i, Components.interfaces.nsIPrefLocalizedString);
- value = value.toString();
- if (value == '') continue;
- var fieldTag = fieldList[j].tagName.toLowerCase();
- if (fieldTag == 'input') {
- if (fieldList[j].value.length == 0) {
- this.debug(' * MATCH!! setting INPUT value: '+value);
- fieldList[j].value = value;
- }
- } else if (fieldTag == 'select') {
- var idx = 0;
- var optionElem = fieldList[j].firstChild;
- while (optionElem &&
- optionElem.text != value &&
- optionElem.value != value)
- {
- optionElem = optionElem.nextSibling;
- idx++;
- }
- if ((idx < fieldList[j].length) && (idx > -1)) {
- this.debug(' * MATCH!! setting SELECT value: '+value);
- fieldList[j].selectedIndex = idx;
- }
- }
- }
- }
- }
- // Recurse on iframes
- var iframeList = doc.getElementsByTagName('iframe');
- for (var i = 0; i < iframeList.length; i++) {
- this.DoAutoFillGecko(iframeList.item(i).contentDocument, fieldValues);
- }
- // Recurse on frames
- var frameList = doc.getElementsByTagName('frame');
- for (var i = 0; i < frameList.length; i++) {
- this.DoAutoFillGecko(frameList.item(i).contentDocument, fieldValues);
- }
- },
-
-
- DoAutoFillAndSubmit : function(uri, datacard, fieldValues) {
- this.debug('DoAutoFillAndSubmit('+datacard+')');
- this.DoAutoFill(uri, datacard, fieldValues, true);
- },
-
-
- DoSubmitTrident : function(hpDoc, fieldValues)
- {
- if (!hpDoc) return;
- this.debug('DoSubmitTrident()');
- var bDone = false;
- var i = 0;
- while (!bDone && i < fieldValues.fields.length) {
- var old = fieldValues.preModifiedValues.queryElementAt(
- i, Components.interfaces.nsIPrefLocalizedString).toString();
- var notOld = fieldValues.htmlValues.queryElementAt(
- i, Components.interfaces.nsIPrefLocalizedString).toString();
- this.debug('--> oldValue: ' + old + ', newValue: ' + notOld);
- if ((old != notOld) &&
- (old.length > 0 || notOld.length > 0)) {
- bDone = true;
- } else {
- i++;
- }
- }
-
- var field;
- if (bDone) {
- this.CheckForSavePrompt(gBrowser.currentURI);
- field = fieldValues.fields[i];
- } else {
- field = fieldValues.fields.join(this.FIELD_DELIMITER);
- }
-
- hpDoc.datacardSubmitForm(field);
- },
-
-
- // Recurses through a Gecko doc and submits the first form it comes to
- // that actually has autofilled fields. Returns a boolean indicating
- // whether the form has been submitted
- DoSubmitGecko : function(doc, fieldValues) {
- if (!doc) return;
- this.debug('DoSubmitGecko()');
- var forms = doc.getElementsByTagName('form');
- // Submit the first form we find that actually has autofilled fields
- for (var i = 0; i < forms.length; i++) {
- var inputFields = this.EnumerateInputFieldsGecko(forms[i]);
- for (var j = 0; j < inputFields.length; j++) {
- var field = inputFields[j];
- var fieldName = field.getAttribute('name');
- var k;
- for (k = 0; k < fieldValues.fields.length; k++) {
- if (fieldValues.fields[k] == fieldName) break;
- }
- if (k >= fieldValues.fields.length)
- continue;
- var old = fieldValues.preModifiedValues.queryElementAt(
- k, Components.interfaces.nsIPrefLocalizedString);
- var notOld = fieldValues.htmlValues.queryElementAt(
- k, Components.interfaces.nsIPrefLocalizedString);
- this.debug('--> oldValue: ' + old.toString() + ', newValue: ' + notOld.toString());
- if ((old.toString() != notOld.toString())
- && field.value && field.value.length) {
- forms[i].submit();
- return true;
- }
- }
- }
- // Recurse on iframes
- var iframeList = doc.getElementsByTagName('iframe');
- for (var i = 0; i < iframeList.length; i++) {
- if (this.DoSubmitGecko(iframeList.item(i).contentDocument, fieldValues))
- return true;
- }
- // Recurse on frames
- var frameList = doc.getElementsByTagName('frame');
- for (var i = 0; i < frameList.length; i++) {
- if (this.DoSubmitGecko(frameList.item(i).contentDocument, fieldValues))
- return true;
- }
- return false;
- },
-
-
- DatacardsExist : function(checkForEmptyDatacard) {
- this.debug('DatacardsExist()');
-
- // If there's no datacards folder, then there are no datacards
- var folder = this.GetDatacardsFolder();
- if (!folder.exists()) return false;
-
- if (checkForEmptyDatacard) {
- var datacardList = this.GetDatacardList();
- if (datacardList.length == 1) { // only one datacard
- var datacardFile = this.GetDatacardFileByName(datacardList[0]);
- if (this.IsEmptyDatacard(datacardFile))
- {
- this.debug('Found an empty datacard!');
- return false; // if the datacard is empty, then let's consider it as if we don't have any datacards yet
- }
- }
- }
-
- // If the datacards folder contains ANY files at all...
- this.debug('Looking for any number of datacards...');
- var entries = folder.directoryEntries;
- return entries.hasMoreElements();
- },
-
-
- // checks to see if the datacard is 'empty' (with the exception of the country field)
- IsEmptyDatacard : function(datacardFile) {
- var regularDict = this.afService.GetDatacardFieldsByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_REGULAR);
- var keys = regularDict.getKeys({});
- for (var k in keys) {
- var key = keys[k];
- this.debug('IsEmptyDatacard() - key is '+key);
- // can't check country because we always default to a value in the drop-down,
- // avoid checking the country field (among other fields...)
- if (key == 'country' || key == 'alt_country' || key == 'cc_type' ||
- key == 'cc_expire_month' || key == 'cc_expire_year')
- continue;
-
- if (this.GetStringFromDict(regularDict, key).length > 0)
- return false;
- }
-
- var advancedDict = this.afService.GetDatacardFieldsByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_ADVANCED);
- keys = advancedDict.getKeys({});
- for (var k in keys) {
- if (this.GetStringFromDict(advancedDict, keys[k]).length > 0)
- return false;
- }
-
- return true;
- },
-
-
- DatacardExists : function(datacard) {
- this.debug('DatacardExists('+datacard+')');
- var file = this.GetDatacardFileByName(datacard);
- return file.exists();
- },
-
-
- // Returns an array of all the INPUT and SELECT fields
- // found in the document, in order of occurence
- EnumerateInputFieldsGecko : function(elm, arr) {
- if (!arr) arr = new Array();
- var tagName = (elm.tagName)
- ? elm.tagName.toLowerCase()
- : '';
- if ((tagName == 'input') || (tagName == 'select')) {
- if (!elm.hasAttribute('name')) return arr;
- if (tagName == 'input') {
- if (elm.hasAttribute('type')) {
- var type = elm.getAttribute('type').toLowerCase();
- if ( !((type == 'text') ||
- (type == 'checkbox')) )
- {
- return arr;
- }
- }
- }
- arr[arr.length] = elm;
- return arr;
- } else {
- // Recurse
- for (var i = 0; i < elm.childNodes.length; i++) {
- arr = this.EnumerateInputFieldsGecko(elm.childNodes[i], arr);
- }
- }
- return arr;
- },
-
-
- // Returns true iff the rippedValues represent a change to
- // the datacard fields that would need to be saved
- FieldValuesMatchDatacard : function(datacard, uri, rippedValues) {
- this.debug('FieldValuesMatchDatacard(..., '+datacard+')');
- // Get the *current* values out of the datacard
- var datacardFile = this.GetDatacardFileByName(datacard);
- var regularDict = this.afService.GetDatacardFieldsByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_REGULAR);
- var advancedDict = this.afService.GetDatacardFieldsByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_ADVANCED);
- // Combine 'regulardict' and 'advancedDict' together into one dict
- var oldDict = regularDict;
- var keys = advancedDict.getKeys({});
- for (var k in keys) {
- var key = keys[k];
- oldDict.setValue(key, advancedDict.getValue(key));
- }
- // Calculate what the new values would be if we were to save
- var values = this.ToPrefLocalizedStringArray(rippedValues.values);
- var newDict = this.FillDatacardFieldsFromHTML(
- datacardFile,
- uri.asciiHost,
- rippedValues.fields.join(this.FIELD_DELIMITER),
- values,
- false); // don't save
- this.debug(' - comparing oldDict to newDict...');
- // Compare newDict to oldDict
- // Only return true if each dictionary's keys/values match
- newKeys = newDict.getKeys({});
- this.debug(' - got '+newKeys.length+' keys');
- for (var k in newKeys) {
- var key = newKeys[k];
- this.debug(' - key '+k+' = '+key);
- var oldVal = '';
- try {
- if (oldDict.hasKey(key))
- {
- oldVal = oldDict.getValue(key);
- oldVal.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
- oldVal = oldVal.toString();
- }
- } catch (ex) { }
- var newVal = '';
- try {
- if (newDict.hasKey(key))
- {
- newVal = newDict.getValue(key);
- newVal.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
- newVal = newVal.toString();
- }
- } catch (ex) { }
- if ((newVal != '') && (newVal != oldVal)) {
- this.debug(' - FOUND a DIFFERENCE: old = "'+oldVal+'" new = "'+newVal+'"');
- return false;
- }
- }
- this.debug(' - NO diff');
- return true;
- },
-
-
- // This function gets called when a form is submitted resulting in
- // potential changes to a datacard. It determines whether to prompt the
- // user and saves the changes, as appropriate.
- CheckForSavePrompt : function(uri, topElement) {
- this.debug('CheckForSavePrompt()');
-
- // First, see if we should just ignore changes for this specific site
- //if (this.IsSiteOnIgnoreChangesList(uri)) return;
- if (this.IsSiteBlacklisted(uri)) return;
-
- // See if we should save changes, in general
- var ps = this.prefService;
-
- // Exit if pref is not int type
- if (ps.getPrefType("datacard.autosave") != ps.PREF_INT)
- return;
-
- // The "datacard.autosave" pref is set in Options dialog->
- // Form Fill panel->Datacards tab->Preferences tab->dropdown menu
- // See datacard-datapanels.inc
-
- // If "do nothing" is selected then exit
- if (ps.getIntPref("datacard.autosave") == 2)
- return;
-
- // If "automatically save" is selected (==1), keep going...
-
- // Check if the user wants a prompt to save
- var doPrompt = (ps.getIntPref("datacard.autosave") == 0);
-
- // Get the current fieldnames and values from the page
- var rippedValues = this.RipFields(topElement);
-
- // Bail if the page is not autofillable
- if (!this.IsPageAutoFillable(uri, rippedValues)) return;
-
- if (this.DatacardsExist(true)) {
- // If the values entered don't match the datacard associated with
- // this site, we prompt user to update it
- var datacard = this.FindDatacardForURI(uri);
- if (datacard == undefined) return;
- if (!this.FieldValuesMatchDatacard(datacard, uri, rippedValues)) {
- if (doPrompt) {
- // The user wants to be prompted before saving
- window.openDialog(
- "chrome://mozapps/content/autofill/datacardUpdateDialog.xul",
- "_blank", "chrome,modal,resizable=no", rippedValues);
- } else {
- // The user doesn't want to be prompted
- this.SetDatacardValues(datacard, uri, rippedValues);
- this.SetDatacardForHost(uri.host, datacard);
- }
- }
- } else {
- // No datacards yet exist. What we want to do now is determine
- // whether the user has in fact entered any data that is worthy
- // of saving in a datacard. If so, we will prompt the user to
- // save it, otherwise we won't waste their time.
-
- this.CreateDatacardTemp(this.TEMP_DATACARD_FILENAME);
- var tempFile = this.GetDatacardFileByName(this.TEMP_DATACARD_FILENAME);
- var values = this.ToPrefLocalizedStringArray(rippedValues.values);
- this.FillDatacardFieldsFromHTML(
- tempFile,
- uri.asciiHost,
- rippedValues.fields.join(this.FIELD_DELIMITER),
- values,
- true); // save (temporarily)
- var regDict = this.afService.GetDatacardFieldsByType(
- tempFile,
- nsIAutoFillService.FIELDTYPE_REGULAR);
- this.DeleteDatacard(this.TEMP_DATACARD_FILENAME);
-
- // regFields now contains the datacard regular values that would be
- // saved if we were to create a real datacard, so see if any data is
- // actually there
- var foundValue = false;
- var keys = regDict.getKeys({});
- for (var k in keys) {
- var key = keys[k];
- var value = regDict.getValue(key);
- if (value.toString() != '') {
- foundValue = true;
- break;
- }
- }
-
- if (foundValue) {
- // There is a value to save, so prompt.
- // (even if the user has the datacard.autosave.prompt pref set to true,
- // we still need to prompt here because we need a new datacard name)
- window.openDialog(
- "chrome://mozapps/content/autofill/datacardSaveNewDialog.xul",
- "_blank", "chrome,modal,resizable=no", rippedValues);
- }
- }
- },
-
-
- // Returns true if there is a fillable form.
- // The 'checkData' boolean argument indicates whether we should additionally
- // check whether we have data for the fields actually present.
- IsPageAutoFillable : function(uri, fieldValues, checkData) {
- this.debug('IsPageAutoFillable('+uri.spec+', ..., checkData:'+checkData+')');
-
- if (!checkData) checkData = false;
-
- if (!(uri.schemeIs('http') ||
- uri.schemeIs('https') ||
- uri.schemeIs('ftp')))
- {
- // Not autofillable :-P
- return;
- }
-
- // If the page has a password field, it will be handled
- // by Passcard instead
- if (CheckIsPasscardable(true)) return false;
-
- // Find out how many potentially autofillable fields there are
- var numAFfields =
- this.afService.NumAutoFillFields(
- uri.asciiHost,
- fieldValues.fields.join(this.FIELD_DELIMITER));
-
- // Any page with fewer than 4 fillable fields is NOT considered
- if (numAFfields < 4) return false;
-
- // If we don't have to check data, then we are done. There are fields.
- if (!checkData) return true;
-
- // From here on, we only want to consider these fields 'fillable' if
- // there is actually data to put in them.
- var datacard = this.FindDatacardForURI(uri);
- var datacardFile = this.GetDatacardFileByName(datacard);
- var values = this.InitPrefLocalizedStringArray(fieldValues.values.length);
- //this.debugPLStringArray(values);
- this.afService.FillHTMLFieldsFromDatacard(datacardFile,
- uri.asciiHost,
- fieldValues.fields.join(this.FIELD_DELIMITER),
- values);
- this.debugPLStringArray(values);
- var e = values.enumerate();
- var i = 0;
- fieldValues.newValues = new Array();
- var fillCount = 0;
- var lastMatchedField = null;
- while (e.hasMoreElements())
- {
- var value = e.getNext();
- value.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
- value = value.toString();
- fieldValues.newValues[i] = value;
- if (value != '') {
- lastMatchedField = fieldValues.fields[i];
- this.debug(' - there is an HTML field ['+fieldValues.fields[i]
- +'] fillable with value ['+value+']');
- fillCount++;
- }
- i++;
- }
-
- if (fillCount >= 2) return true;
-
- // This is to avoid the problem where the search field on netscape.com,
- // which is named "name" was getting autofilled
- if ((fillCount == 1) && (lastMatchedField != 'name')) return true;
-
- // Fallback
- return false;
- },
-
-
- DoHighlight : function(fieldValues) {
- this.debug('DoHighlight()');
- var ps = this.prefService;
- if (ps.getPrefType('datacard.highlight') &&
- !ps.getBoolPref('datacard.highlight'))
- {
- return; // Should not highlight
- }
- // Check if Trident
- var hpDoc = getTridentDocument();
- if (hpDoc) {
- try {
- // Highlight Trident
- this.DoHighlightTrident(hpDoc, fieldValues);
- } catch (ex) {
- this.debug(' ***** Error highlighting on Trident!!!');
- }
- } else {
- // Highlight Gecko
- this.DoHighlightGecko(gBrowser.contentDocument, fieldValues);
- }
- },
-
-
- // recursive highlighting of form fields throughout subframes
- DoHighlightTrident : function(doc, fieldValues) {
- if (!doc) return;
- this.debug('DoHighlightTrident()');
- var fields = "";
- for (var j = 0; j < fieldValues.fields.length; j++) {
- var value = fieldValues.newValues[j];
- if (value && value.length > 0)
- {
- if (fields.length > 0)
- fields += this.FIELD_DELIMITER;
- fields += fieldValues.fields[j];
- }
- }
- this.debug('--> Fields to highlight in Trident: ' + fields);
- doc.datacardHighlight(fields);
- },
-
-
- // recursive highlighting of form fields throughout subframes
- DoHighlightGecko : function(doc, fieldValues) {
- if (!doc) return;
- this.debug('DoHighlightGecko()');
- var fieldList = this.EnumerateInputFieldsGecko(doc);
- for (var i = 0; i < fieldList.length; i++) {
- var field = fieldList[i];
- var fieldName = field.getAttribute('name');
- //this.debug(' - checking: '+fieldName);
- for (var j = 0; j < fieldValues.fields.length; j++) {
- if (fieldValues.fields[j] != fieldName) continue;
- if (fieldValues.newValues[j] == undefined) continue;
- if (fieldValues.newValues[j] == '') continue;
- this.debug(' - highlighting ['+fieldValues.fields[j]
- +'] because we have a value ['+fieldValues.newValues[j]+']');
- field.style.backgroundColor = this.FIELD_HIGHLIGHT_COLOR;
- break;
- }
- }
- // Recurse on iframes
- var iframeList = doc.getElementsByTagName('iframe');
- for (var i = 0; i < iframeList.length; i++) {
- this.DoHighlightGecko(iframeList.item(i).contentDocument, fieldValues);
- }
- // Recurse on frames
- var frameList = doc.getElementsByTagName('frame');
- for (var i = 0; i < frameList.length; i++) {
- this.DoHighlightGecko(frameList.item(i).contentDocument, fieldValues);
- }
- },
-
-
- // Here is where we decide whether to show the message bar, automatically
- // fill the page, fill and submit, or do nothing
- DoPageLoadedCheck : function() {
- this.debug('DoPageLoadedCheck()');
-
- // remove the message bar in case we return early (we will redraw the bar if necessary)
- // dump('--> gCurrentNotificationBar: ' + gCurrentNotificationBar + '\n');
- if (gCurrentNotificationBar == 'datacard') {
- this.debug('--> Hiding Datacard Messagebar');
- gBrowser.hideMessage(gBrowser.selectedBrowser, "top");
- }
-
- // Bail if the page has a password field. It will be handled
- // by Passcard instead
- if (CheckIsPasscardable(true)) return;
- this.debug('--> Not a Passcard');
-
- // Bail if the site is blacklisted
- if (this.IsSiteBlacklisted(gBrowser.currentURI)) return;
- this.debug('--> Site is not blacklisted');
-
- // Bail if there is no datacard to use
- var datacard = this.FindDatacardForURI(gBrowser.currentURI);
- if (!datacard) return;
- this.debug('--> There is a Datacard to use');
-
- // Get the current fieldnames and values from the page
- var fieldValues = this.RipFields();
-
- // Bail if the page is not autofillable (assumes datacard is loaded)
- var fillable = this.IsPageAutoFillable(gBrowser.currentURI, fieldValues, true);
- if (!fillable) {
- this.debug('--> Page is NOT datacardable');
- return;
- }
- this.debug('--> Page is datacardable');
-
- // See if we should highlight
- var ps = datacardUtils.prefService;
- if (ps.getPrefType('datacard.highlight') &&
- ps.getBoolPref('datacard.highlight'))
- {
- this.DoHighlight(fieldValues);
- }
- this.debug('--> Finished datacard highlighting');
-
- var datacardFile = this.GetDatacardFileByName(datacard);
- var datacardProps = this.afService.GetDatacardFieldsByType(
- datacardFile,
- nsIAutoFillService.FIELDTYPE_PROPERTY);
-
- var whenToFill = this.GetStringFromDict(datacardProps, 'WhenToFill');
- switch (whenToFill) {
- case 'displayprompt':
- // Show the browser message bar
- displayNotificationBar("datacard");
- break;
- case 'filldatacard':
- this.DoAutoFill(gBrowser.currentURI, datacard, fieldValues);
- break;
- case 'fillsubmitdatacard':
- this.DoAutoFillAndSubmit(gBrowser.currentURI, datacard, fieldValues);
- break;
- case 'donothing':
- default:
- // Do nothing :)
- }
- },
-
-
- GetStringFromDict : function(dict, key) {
- dict.QueryInterface(Components.interfaces.nsIDictionary);
- var value = dict.getValue(key);
- value.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
- return value.toString();
- },
-
-
- CreatePrefLocalizedString : function(str) {
- if (!str) str = '';
- var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
- .createInstance(Components.interfaces.nsIPrefLocalizedString);
- pls.setDataWithLength(str.length, str);
- return pls;
- },
-
-
- InitPrefLocalizedStringArray : function(size) {
- var blankArray = new Array();
- for (var i = 0; i < size; i++) {
- blankArray[i] = '';
- }
- return this.ToPrefLocalizedStringArray(blankArray);
- },
-
-
- ToPrefLocalizedStringArray : function(arr) {
- var newArr = Components.classes["@mozilla.org/array;1"]
- .createInstance(Components.interfaces.nsIMutableArray);
- for (var i = 0; i < arr.length; i++)
- {
- newArr.appendElement(
- this.CreatePrefLocalizedString(arr[i]), false);
- }
- return newArr;
- },
-
-
- // This gets called from nsHTMLPluginDocument (C++) when a datacard form
- // has been submitted in Trident. The dictionary contains only those
- // values that have actually changed
- TridentSubmit : function() {
- this.debug('TridentSubmit()');
-
- // TODO: We should figure out the URL of the page on which the form
- // was submitted, and check if this should actually be ignored
- var uri = gBrowser.currentURI;
- this.CheckForSavePrompt(uri, null);
- },
-
-
- SaveDatacard : function() {
- this.debug('SaveDatacard()');
- var hpDoc = getTridentDocument();
- if (hpDoc)
- this.CheckForSavePrompt(gBrowser.currentURI);
- else
- this.CheckForSavePrompt(gBrowser.currentURI, gBrowser.contentDocument);
- },
-
-
- // Retrieves a sequence of field-value pairs for the current
- // document. The return value is an object with a 'fields'
- // array and a corresponding 'values' array. If the 'elt'
- // param is provided (Gecko only) it will restrict the searching
- // to a particular dom node.
- RipFields : function(elt) {
- this.debug('RipFields()');
- var fieldValues = new Object();
- fieldValues.fields = new Array();
- fieldValues.values = new Array();
- var hpDoc = getTridentDocument();
- if (hpDoc) {
- // Trident
- var combinedFieldValues = hpDoc.RipFieldsTrident();
- if (combinedFieldValues) {
- combinedFieldValues = combinedFieldValues.split(this.RULE_SEPARATOR);
- fieldValues.fields = combinedFieldValues[0].split(this.FIELD_DELIMITER);
- fieldValues.values = combinedFieldValues[1].split(this.FIELD_DELIMITER);
- // The field values get URL encoded inside Trident, so unencode here
- for (var i = 0; i < fieldValues.values.length; i++) {
- fieldValues.values[i] = unescape(fieldValues.values[i]);
- }
- }
- } else {
- // Gecko
- this.RipFieldsGecko(elt, fieldValues);
- }
- this.debug('fieldValues:');
- this.debug(' .fields: '+fieldValues.fields);
- this.debug(' .values: '+fieldValues.values);
- return fieldValues;
- },
-
-
- RipFieldsGecko : function(elt, fieldValues) {
- this.debug('RipFieldsGecko()');
- var parentElt = elt;
- if (!elt) parentElt = gBrowser.contentDocument;
- var fieldList = this.EnumerateInputFieldsGecko(parentElt);
- for (var i = 0; i < fieldList.length; i++) {
- var fieldIdx = fieldValues.fields.length;
- var name = fieldList[i].getAttribute('name');
- var val = fieldList[i].value;
- this.debug(' ripped['+fieldIdx+']: '+name+' = '+val);
- fieldValues.fields[fieldIdx] = name;
- fieldValues.values[fieldIdx] = val;
- }
- // Recurse on iframes
- var iframeList = parentElt.getElementsByTagName('iframe');
- for (var i = 0; i < iframeList.length; i++) {
- this.RipFieldsGecko(iframeList.item(i).contentDocument, fieldValues);
- }
- // Recurse on frames
- var frameList = parentElt.getElementsByTagName('frame');
- for (var i = 0; i < frameList.length; i++) {
- this.RipFieldsGecko(frameList.item(i).contentDocument, fieldValues);
- }
- },
-
-
- debug : function(msg) {
- if (this.DEBUG)
- dump('datacardUtils: '+msg+'\n');
- },
-
-
- debugPLStringArray : function(plStringArray) {
- var e = plStringArray.enumerate();
- var i = 0;
- while (e.hasMoreElements()) {
- var item = e.getNext();
- item.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
- this.debug(' ['+i+']: '+item.toString());
- i++;
- }
- }
- };
-
-
- // This is the observer for form submissions. It gets registered with the
- // Observer Service to watch for the "formsubmit" topic. But unfortunately
- // the implementation of nsHTMLFormElement was only notifying observers who
- // implemented nsIFormSubmitObserver, and I couldn't get that working in
- // JavaScript. So instead, I ended up hacking nsHTMLFormElement to fall back
- // to notifying nsIObservers as well.
- function datacardSubmitObserver() {
- }
- datacardSubmitObserver.prototype = {
-
- QueryInterface : function(aIID) {
- this.debug('QueryInterface('+aIID+')');
- if (aIID.equals(Components.interfaces.nsIObserver) ||
- aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
- aIID.equals(Components.interfaces.nsIWeakReference) ||
- aIID.equals(Components.interfaces.nsISupports))
- {
- return this;
- }
- throw Components.results.NS_NOINTERFACE;
- },
-
- QueryReferent : function(aIID) {
- this.debug('QueryReferent('+aIID+')');
- return this;
- },
-
- observe : function(formElement, topic, url) {
- //try {
- this.debug('observe(formElement:'+formElement+' topic:'+topic+' url:'+url+')');
-
- // A form has been submitted, so determine whether we want to save
- // changes to the datacard, ignore changes, or prompt the user
-
- // First, get the URI of the page on which the form was submitted
- var uri = datacardUtils.ioService.newURI(url, null, null);
- datacardUtils.CheckForSavePrompt(uri, formElement);
-
- //} catch (ex) {
- // dump('Caught Exception in datacardUtils.js: datacardSubmitObserver.observe()\n');
- // for (var prop in ex)
- // dump(' '+prop+': '+ex[prop]+'\n');
- //}
- },
-
- debug : function(msg) {
- datacardUtils.debug('datacardSubmitObserver: '+msg);
- }
- };
-
-
- // This gets called from nsHTMLPluginDocument (C++) when a datacard form
- // has been submitted in Trident. The dictionary contains only those values
- // that have actually changed
- function TridentSubmit() {
- datacardUtils.TridentSubmit();
- }
-
-
- datacardUtils.Init();