home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GameStar 2005 October
/
Gamestar_77_2005-10_dvd.iso
/
Programy
/
nsb-install-8-0.exe
/
chrome
/
toolkit.jar
/
content
/
mozapps
/
autofill
/
datacardUtils.js
< prev
next >
Wrap
Text File
|
2005-07-29
|
45KB
|
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();