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 >
Text File  |  2005-07-29  |  45KB  |  1,410 lines

  1. // This file contains what could essentially be called the "Datacard API"
  2. // Mostly written by Chris Campbell and Justin Louie
  3.  
  4. const nsIAutoFillService = Components.interfaces.nsIAutoFillService;
  5.  
  6.  
  7. var datacardUtils = {
  8.  
  9.     // Set true to enable debug output
  10.     DEBUG : false,
  11.  
  12.     // Constants
  13.     DATACARDS_FOLDER : 'datacards',
  14.     DATACARD_FILE_EXT : '.dat',
  15.     TEMP_DATACARD_FILENAME : '~~temp~~',
  16.     SITELIST_FILE : 'datacard-sites.txt',
  17.     SITELIST_PREFNAME : 'datacard.sites',
  18.     IGNORELIST_PREFNAME : 'datacard.sites.ignorechanges',
  19.     DEFAULT_DATACARD_PREFNAME : 'datacard.default',
  20.     SPECIAL_CHARS_REGEXP : /\$|,|@|#|~|`|\%|\*|\^|\&|\(|\)|\+|\=|\[|\-|\_|\]|\[|\}|\{|\;|\:|\'|\"|\<|\>|\?|\||\\|\!|\$|\.|\s/g,
  21.     FIELD_HIGHLIGHT_COLOR : '#ffff66',
  22.     RULE_SEPARATOR : '=',
  23.     FIELD_DELIMITER : '|',
  24.  
  25.     // Globals
  26.     profileDir : null,
  27.     ioService : null,
  28.     prefService : null,
  29.     afService : null,
  30.     observerService : null,
  31.     submitObserver : null,
  32.  
  33.  
  34.     Init : function() {
  35.         this.debug('Init()');
  36.  
  37.         // Get the user's profile directory
  38.         if (!this.profileDir) {
  39.             this.profileDir =
  40.                 Components.classes["@mozilla.org/file/directory_service;1"]
  41.                           .getService(Components.interfaces.nsIProperties)
  42.                           .get("ProfD", Components.interfaces.nsILocalFile);
  43.         }
  44.         this.debug(' profileDir: '+this.profileDir.path);
  45.  
  46.         // IO Service
  47.         if (!this.ioService) {
  48.             this.ioService =
  49.                 Components.classes["@mozilla.org/network/io-service;1"]
  50.                           .getService(Components.interfaces.nsIIOService);
  51.         }
  52.  
  53.         // Pref Service
  54.         if (!this.prefService) {
  55.             this.prefService =
  56.                 Components.classes["@mozilla.org/preferences-service;1"]
  57.                           .getService(Components.interfaces.nsIPrefBranch);
  58.         }
  59.  
  60.         // Autofill service
  61.         if (!this.afService) {
  62.             this.afService =
  63.                 Components.classes["@mozilla.org/autofillService;1"]
  64.                           .getService(nsIAutoFillService);
  65.         }
  66.  
  67.         // Observer service
  68.         if (!this.observerService) {
  69.             this.observerService =
  70.                 Components.classes["@mozilla.org/observer-service;1"]
  71.                           .getService(Components.interfaces.nsIObserverService);   
  72.         }
  73.     },
  74.  
  75.  
  76.     InitSubmitObserver : function() {
  77.         // Form submission observer
  78.         if (!this.submitObserver) {
  79.             // TODO: We may want to do something here to make sure this observer
  80.             // only gets added *once* per application, *not* once per browser
  81.             // window, as now happens
  82.             this.submitObserver = new datacardSubmitObserver();
  83.             this.observerService.addObserver(this.submitObserver, "formsubmit", true);
  84.         }
  85.     },
  86.  
  87.  
  88.     GetDatacardsFolder : function() {
  89.         this.debug('GetDatacardsFolder()');
  90.         // Create file descriptor
  91.         var folder = this.profileDir.clone();
  92.         folder.append(this.DATACARDS_FOLDER);
  93.         return folder; // returns nsILocalFile
  94.     },
  95.  
  96.  
  97.     EnsureDatacardsFolderExists : function() {
  98.         this.debug('EnsureDatacardsFolderExists()');
  99.         var folder = this.GetDatacardsFolder();
  100.         if (!folder.exists()) {
  101.             this.debug(' creating folder: '+this.DATACARDS_FOLDER);
  102.             folder.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0);
  103.         }
  104.         return folder; // returns nsILocalFile
  105.     },
  106.  
  107.  
  108.     GetSiteListFile : function() {
  109.         this.debug('GetSiteListFile()');
  110.         // Create file descriptor
  111.         var file = this.profileDir.clone();
  112.         file.append(this.SITELIST_FILE);
  113.         return file; // returns nsILocalFile
  114.     },
  115.  
  116.  
  117.     EnsureSiteListFileExists : function() {
  118.         this.debug('EnsureSiteListFileExists()');
  119.         var file = this.GetSiteListFile();
  120.         if (!file.exists()) {
  121.             this.EnsureDatacardsFolderExists();
  122.             this.debug(' creating file: '+this.SITELIST_FILE);
  123.             file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0);
  124.         }
  125.         return file; // returns nsILocalFile
  126.     },
  127.  
  128.  
  129.     GetSiteList : function() {
  130.         // As with SetSiteList(), this should probably use a file
  131.         // rather than a pref, but I was having probs getting it to work...
  132.         var siteList = '';
  133.         if (this.prefService.getPrefType(this.SITELIST_PREFNAME)) {
  134.             siteList = this.prefService.getCharPref(this.SITELIST_PREFNAME);
  135.         }
  136.         this.debug('GetSiteList(): '+siteList);
  137.         return siteList;
  138.     },
  139.  
  140.  
  141.     SetSiteList : function(siteList) {
  142.         // As with GetSiteList(), this should probably use a file
  143.         // rather than a pref, but I was having probs getting it to work...
  144.         this.debug('SetSiteList(...)');
  145.         this.prefService.setCharPref(this.SITELIST_PREFNAME, siteList);
  146.     },
  147.  
  148.  
  149.     GetSiteIgnoreChangesList : function() {
  150.         var ignoreList = '';
  151.         if (this.prefService.getPrefType(this.IGNORELIST_PREFNAME)) {
  152.             ignoreList = this.prefService.getCharPref(this.IGNORELIST_PREFNAME);
  153.         }
  154.         this.debug('GetSiteIgnoreChangesList(): '+ignoreList);
  155.         return ignoreList;
  156.     },
  157.  
  158.  
  159.     SetSiteIgnoreChangesList : function(ignoreList) {
  160.         this.debug('SetSiteIgnoreChangesList(...)');
  161.         this.prefService.setCharPref(this.IGNORELIST_PREFNAME, ignoreList);
  162.     },
  163.  
  164.  
  165.     FindDatacardForURI : function(uri) {
  166.         this.debug('FindDatacardForURI('+uri.spec+')');
  167.         var siteList = this.GetSiteList();
  168.         var sites = siteList.split(',');
  169.         for (var i = 0; i < sites.length; i++) {
  170.             var arr = sites[i].split(':');
  171.             if (arr[0] == uri.asciiHost) {
  172.                 this.debug(' -- datacard for this site is: '+arr[1]);
  173.                 if (!arr[1].length) return undefined;
  174.                 return arr[1];
  175.             }
  176.         }
  177.         this.debug(' -- using default datacard');
  178.         return this.GetDefaultDatacard();
  179.     },
  180.  
  181.  
  182.     GetDefaultDatacard : function() {
  183.         this.debug('GetDefaultDatacard()');
  184.         if (this.prefService.getPrefType(this.DEFAULT_DATACARD_PREFNAME)) {
  185.             return this.prefService.getCharPref(this.DEFAULT_DATACARD_PREFNAME);
  186.         }
  187.         return null;
  188.     },
  189.  
  190.  
  191.     SetDefaultDatacard : function(datacard) {
  192.         this.debug('SetDefaultDatacard('+datacard+')');
  193.         this.prefService.setCharPref(this.DEFAULT_DATACARD_PREFNAME,datacard);
  194.     },
  195.  
  196.  
  197.     // Establish a host ==> datacard association
  198.     SetDatacardForHost : function(host, datacard) {
  199.         this.debug('SetDatacardForHost(host:'+host+', datacard:'+datacard+')');
  200.         var siteList = this.GetSiteList();
  201.         var sites;
  202.         if (siteList.length)
  203.             sites = siteList.split(',');
  204.         else
  205.             sites = new Array();
  206.         var found = false;
  207.         for (var i = 0; i < sites.length; i++) {
  208.             var arr = sites[i].split(':');
  209.             if (arr[0] == host) {
  210.                 found = true;
  211.                 // This site is already listed, so just update it
  212.                 sites[i] = arr[0]+':'+datacard;
  213.                 break;
  214.             }
  215.         }
  216.         if (!found) {
  217.             sites[sites.length] = host+':'+datacard;
  218.         }
  219.         this.SetSiteList(sites.join(','));
  220.     },
  221.  
  222.  
  223.     // Returns an array of the extant datacards
  224.     GetDatacardList : function() {
  225.         this.debug('GetDatacardList()');
  226.         var list = new Array();
  227.  
  228.         // If there's no datacards folder, then there are no datacards
  229.         var folder = this.GetDatacardsFolder();
  230.         if (!folder.exists()) return list;
  231.  
  232.         // If the datacards folder contains ANY files at all...
  233.         var entries = folder.directoryEntries;
  234.         while (entries.hasMoreElements()) {
  235.             var entry = entries.getNext();
  236.             entry.QueryInterface(Components.interfaces.nsILocalFile);
  237.             var fileName = entry.leafName;
  238.             if (fileName.substring(fileName.length-4)=='.dat') {
  239.                 list[list.length] = fileName.substring(0,fileName.length-4);
  240.             }
  241.         }
  242.         return list;
  243.     },
  244.  
  245.  
  246.     // Adds the specified site to the 'ignore changes' list
  247.     IgnoreChangesToSite : function(site) {
  248.         var host = this.ExtractHostname(site);
  249.         this.debug('IgnoreChangesToSite('+host+')');
  250.         var ignoreList = this.GetSiteIgnoreChangesList().split(',');
  251.         var found = false;
  252.         for (var i = 0; i < ignoreList.length; i++) {
  253.             if (ignoreList[i] == host) {
  254.                 found = true;
  255.                 break;
  256.             }
  257.         }
  258.         if (!found) {
  259.             ignoreList[ignoreList.length] = host;
  260.             this.SetSiteIgnoreChangesList(ignoreList.join(','));
  261.         }
  262.     },
  263.  
  264.  
  265.     IsSiteOnIgnoreChangesList : function(site) {
  266.         var host = this.ExtractHostname(site);
  267.         var ignoreList = this.GetSiteIgnoreChangesList().split(',');
  268.         var found = false;
  269.         for (var i = 0; i < ignoreList.length; i++) {
  270.             if (ignoreList[i] == host) {
  271.                 found = true;
  272.                 break;
  273.             }
  274.         }
  275.         this.debug('IsSiteOnIgnoreChangesList('+host+'): '+((found)?'Yes':'No'));
  276.         return found;
  277.     },
  278.  
  279.  
  280.     // Adds the specified site to the blacklist
  281.     BlacklistSite : function(site) {
  282.         var host = this.ExtractHostname(site);
  283.         this.debug('BlacklistSite('+host+')');
  284.         this.SetDatacardForHost(host, '');
  285.     },
  286.  
  287.  
  288.     IsSiteBlacklisted : function(site) {
  289.         var host = this.ExtractHostname(site);
  290.         var blacklist = this.GetBlacklistedSites();
  291.         var found = false;
  292.         for (var i = 0; i < blacklist.length; i++) {
  293.             if (blacklist[i] == host) {
  294.                 found = true;
  295.                 break;
  296.             }
  297.         }
  298.         this.debug('IsSiteBlacklisted('+host+'): '+((found)?'Yes':'No'));
  299.         return found;
  300.     },
  301.  
  302.  
  303.     // Returns an array of the blacklisted host names
  304.     GetBlacklistedSites : function() {
  305.         this.debug('GetBlacklistedSites()');
  306.         // Blacklisted sites are those sites which have no datacard name
  307.         // associated with them in the site list
  308.         var siteList = this.GetSiteList();
  309.         var sites = siteList.split(',');
  310.         var blacklist = new Array();
  311.         for (var i = 0; i < sites.length; i++) {
  312.             var arr = sites[i].split(':');
  313.             if (!arr[1] || !arr[1].replace(/\s+$/).length) {
  314.                 this.debug(' - blacklist['+blacklist.length+']: '+arr[0]);
  315.                 blacklist[blacklist.length] = arr[0];
  316.             } else {
  317.                 this.debug('   (excluding '+arr[0]+')');
  318.             }
  319.         }
  320.         return blacklist;
  321.     },
  322.  
  323.  
  324.     // Pass in either a URI object or a hostname string, and
  325.     // you will get back just the hostname string.
  326.     ExtractHostname : function(site) {
  327.         var uri = null;
  328.         var host;
  329.         try {
  330.             uri = site.QueryInterface(Components.interfaces.nsIURI);
  331.             host = uri.asciiHost;
  332.         } catch (ex) {
  333.             // Apparently 'site' wasn't a URI object, so assume it was
  334.             // just a hostname
  335.             host = site;
  336.         }
  337.         this.debug('ExtractHostname(...): '+host);
  338.         return host;
  339.     },
  340.  
  341.  
  342.     // Removes a site (blacklisted or not) from the site list
  343.     UnlistSite : function(site) {
  344.         var host = this.ExtractHostname(site);
  345.         this.debug('UnlistSite('+host+')');
  346.         var siteList = this.GetSiteList();
  347.         var sites = siteList.split(',');
  348.         var newSites = new Array();
  349.         for (var i = 0; i < sites.length; i++) {
  350.             var arr = sites[i].split(':');
  351.             if (arr[0] != host)
  352.                 newSites[newSites.length] = sites[i];
  353.         }
  354.         this.SetSiteList(newSites.join(','));
  355.     },
  356.  
  357.  
  358.     GetDatacardFileByName : function(name) {
  359.         this.debug('GetDatacardFileByName('+name+')');
  360.         var fileName = name + this.DATACARD_FILE_EXT;
  361.         var file = this.GetDatacardsFolder();
  362.         file.append(fileName);
  363.         return file;
  364.     },
  365.  
  366.  
  367.     GenerateFilenamePrefixForDatacard : function(label) {
  368.         var name = label.replace(this.SPECIAL_CHARS_REGEXP, '');
  369.         this.debug('GenerateFilenamePrefixForDatacard('+label+'): '+name);
  370.         return name;
  371.     },
  372.  
  373.  
  374.     CreateDatacard : function(name) {
  375.         this.debug('CreateDatacard('+name+')');
  376.         this.CreateDatacardTemp(name);
  377.         if (!this.GetDefaultDatacard())
  378.             this.SetDefaultDatacard(name);
  379.     },
  380.  
  381.         
  382.     CreateDatacardTemp : function(name) {
  383.         this.debug('CreateDatacardTemp('+name+')');
  384.         this.EnsureDatacardsFolderExists();
  385.         var datacardFile = this.GetDatacardFileByName(name);
  386.         if (!datacardFile.exists()) {
  387.             this.debug(' creating datacard file: '+datacardFile.path);
  388.             datacardFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0);
  389.         }
  390.         this.afService.SetDatacardFieldByType(
  391.             datacardFile,
  392.             nsIAutoFillService.FIELDTYPE_PROPERTY,
  393.             'WhenToFill',
  394.             'displayprompt');
  395.         this.afService.SetDatacardFieldByType(
  396.             datacardFile,
  397.             nsIAutoFillService.FIELDTYPE_PROPERTY,
  398.             'Label',
  399.             name);
  400.         
  401.         // Check a pref to see whether datacards should be password
  402.         // protected by default
  403.         var isEncrypted = false;
  404.         if (this.prefService.getPrefType('datacard.passwordprotect') &&
  405.             this.prefService.getBoolPref('datacard.passwordprotect'))
  406.         {
  407.             isEncrypted = true;
  408.             this.debug(' ENCRYPTED');
  409.         } else {
  410.             this.debug(' UNencrypted');
  411.         }
  412.         this.afService.SetDatacardFieldByType(
  413.             datacardFile,
  414.             nsIAutoFillService.FIELDTYPE_PROPERTY,
  415.             'Encrypted',
  416.             (isEncrypted) ? '1' : '0');
  417.     },
  418.  
  419.  
  420.     // This is a wrapper function on the nsAutoFillService function
  421.     // of the same name, specifically for the purpose of doing
  422.     // country name substitution when necessary
  423.     FillDatacardFieldsFromHTML : function(datacardFile,
  424.                                           host,
  425.                                           fieldNames,
  426.                                           valuesArray,
  427.                                           doSave)
  428.     {
  429.         this.debug('FillDatacardFieldsFromHTML(...)');
  430.         var dict = this.afService
  431.                        .FillDatacardFieldsFromHTML(datacardFile,
  432.                                                    host,
  433.                                                    fieldNames,
  434.                                                    valuesArray,
  435.                                                    doSave);
  436.         // Check if we need to do any country name substitution
  437.         var keys = dict.getKeys({});
  438.         for (var k in keys) {
  439.             var key = keys[k];
  440.             if (key == 'country' || key == 'alt_country') {
  441.                 var dubiousCountryObj = dict.getValue(key);
  442.                 dubiousCountryObj.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
  443.                 var dubiousCountry = dubiousCountryObj.toString();
  444.                 var matchCountry = findClosestCountryMatch(dubiousCountry);
  445.                 if (dubiousCountry != matchCountry) {
  446.                     // Set the dictionary value straight
  447.                     this.debug(' - matchCountry: '+matchCountry);
  448.                     dubiousCountryObj.setDataWithLength(matchCountry.length, matchCountry);
  449.                     if (doSave) {
  450.                         // Set the datacard value straight
  451.                         this.afService
  452.                             .SetDatacardFieldByType(
  453.                                 datacardFile,
  454.                                 nsIAutoFillService.FIELDTYPE_REGULAR,
  455.                                 key,
  456.                                 matchCountry);
  457.                     }
  458.                 }
  459.             }
  460.         }
  461.         return dict;        
  462.     },
  463.  
  464.  
  465.     SetDatacardValues : function(datacard, uri, rippedValues) {
  466.         this.debug('SetDatacardValues('+datacard+')');
  467.         var datacardFile = this.GetDatacardFileByName(datacard);
  468.         var values = this.ToPrefLocalizedStringArray(rippedValues.values);
  469.         this.FillDatacardFieldsFromHTML(
  470.                          datacardFile,
  471.                          uri.asciiHost,
  472.                          rippedValues.fields.join(this.FIELD_DELIMITER),
  473.                          values,
  474.                          true);  // save to file
  475.     },
  476.  
  477.  
  478.     DeleteDatacard : function(name) {
  479.         this.debug('DeleteDatacard('+name+')');
  480.         var siteList = this.GetSiteList();
  481.         var sites = siteList.split(',');
  482.         for (var i = 0; i < sites.length; i++) {
  483.             var arr = sites[i].split(':');
  484.             if (arr[1] && arr[1]==name) {
  485.                 this.UnlistSite(arr[0]);
  486.             }
  487.         }
  488.         var file = this.GetDatacardFileByName(name);
  489.         file.remove(false);
  490.         if (name == this.GetDefaultDatacard()) {
  491.             // Pick a new default
  492.             var list = this.GetDatacardList();
  493.             var newDefault = (list.length) ? list[0] : '';
  494.             this.SetDefaultDatacard(newDefault);
  495.         }
  496.     },
  497.  
  498.  
  499.     DeleteAllDatacards : function(onlyDoEncrypted) {
  500.         this.debug('DeleteAllDatacards('+(onlyDoEncrypted)?'only encrypted)':')');
  501.         var list = this.GetDatacardList();
  502.         for (var i = 0; i < list.length; i++) {
  503.             if (onlyDoEncrypted) {
  504.                 var file = this.GetDatacardFileByName(list[i]);
  505.                 file.QueryInterface(Components.interfaces.nsILocalFile);
  506.                 var props =
  507.                     this.afService.GetDatacardFieldsByType(
  508.                         file,
  509.                         nsIAutoFillService.FIELDTYPE_PROPERTY);
  510.                 var isEncrypted = props.getValue('Encrypted');
  511.                 isEncrypted.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
  512.                 isEncrypted = isEncrypted.toString();
  513.                 // If this datacard isn't encrypted, skip it
  514.                 if (isEncrypted != '1') continue;
  515.             }
  516.             this.DeleteDatacard(list[i]);
  517.         }
  518.     },
  519.  
  520.  
  521.     DoAutoFill : function(uri, datacard, fieldValues, submit) {
  522.         if (!uri)
  523.             uri = gBrowser.currentURI;
  524.         var defaultDatacard = this.FindDatacardForURI(uri);
  525.         if (!datacard)
  526.             datacard = defaultDatacard;
  527.         if (!fieldValues)
  528.             fieldValues = this.RipFields();
  529.  
  530.         this.debug('DoAutoFill('+uri.host+', '+datacard+')');
  531.         
  532.         // Since we're filling with this datacard, make sure it's the
  533.         // default for this site from now on
  534.         if (datacard != defaultDatacard)
  535.             this.SetDatacardForHost(uri.host, datacard);
  536.  
  537.         var datacardFile = this.GetDatacardFileByName(datacard);
  538.         fieldValues.preModifiedValues = this.ToPrefLocalizedStringArray(fieldValues.values);
  539.         var htmlValues = this.InitPrefLocalizedStringArray(fieldValues.values.length);
  540.         this.afService.FillHTMLFieldsFromDatacard(
  541.             datacardFile,
  542.             uri.asciiHost,
  543.             fieldValues.fields.join(this.FIELD_DELIMITER),
  544.             htmlValues);
  545.         this.debug(' -> got these values to fill with:');
  546.         this.debugPLStringArray(htmlValues);
  547.         fieldValues.htmlValues = htmlValues;
  548.  
  549.         // Check if Trident
  550.         var hpDoc = getTridentDocument();
  551.         if (hpDoc) {
  552.             try {
  553.                 this.DoAutoFillTrident(hpDoc, fieldValues);
  554.             } catch (ex) {
  555.                 this.debug(' ***** Error autofilling on Trident!!!');
  556.             }
  557.         } else {
  558.             this.DoAutoFillGecko(gBrowser.contentDocument, fieldValues);
  559.         }
  560.         if (submit) {
  561.             this.debug(' - doing submit');
  562.             if (hpDoc) {
  563.                 try {
  564.                     this.DoSubmitTrident(hpDoc, fieldValues);
  565.                 } catch (ex) {
  566.                     this.debug(' ***** Error autosubmitting on Trident!!!');
  567.                 }
  568.             } else {
  569.                 this.DoSubmitGecko(gBrowser.contentDocument, fieldValues);
  570.             }
  571.         }
  572.     },
  573.  
  574.  
  575.     DoAutoFillTrident : function(htmlPluginDoc, fieldValues) {
  576.         this.debug('DoAutoFillTrident()');
  577.         var mangledString = fieldValues.fields.join(this.FIELD_DELIMITER);
  578.         var e = fieldValues.htmlValues.enumerate();
  579.         var vals = new Array();
  580.         while (e.hasMoreElements())
  581.         {
  582.             vals[vals.length] = escape(e.getNext()
  583.                 .QueryInterface(Components.interfaces.nsIPrefLocalizedString)
  584.                 .toString());
  585.         }
  586.         mangledString += this.RULE_SEPARATOR + vals.join(this.FIELD_DELIMITER);
  587.         htmlPluginDoc.FillFieldsTrident(mangledString);
  588.     },
  589.  
  590.  
  591.     // Actually fill gecko fields!!!
  592.     DoAutoFillGecko : function(doc, fieldValues) {
  593.         if (!doc) return;
  594.         this.debug('DoAutoFillGecko()');
  595.  
  596.         // get list of input elements
  597.         var fieldList = this.EnumerateInputFieldsGecko(doc);
  598.  
  599.         // for each field to use to fill with
  600.         for (var i = 0; i < fieldValues.fields.length; i++) {
  601.             var fieldName = fieldValues.fields[i];
  602.             this.debug('* testing field: '+fieldName);
  603.             // iterate through the input elements in the document
  604.             for (var j = 0; j < fieldList.length; j++) {
  605.                 //this.debug('** matching against: '+fieldList[j].getAttribute('name'));
  606.                 if (fieldList[j].getAttribute('name') == fieldName) {
  607.                     var value =
  608.                         fieldValues.htmlValues.queryElementAt(
  609.                             i, Components.interfaces.nsIPrefLocalizedString);
  610.                     value = value.toString();
  611.                     if (value == '') continue;
  612.                     var fieldTag = fieldList[j].tagName.toLowerCase();
  613.                     if (fieldTag == 'input') {
  614.                         if (fieldList[j].value.length == 0) {
  615.                             this.debug(' * MATCH!! setting INPUT value: '+value);
  616.                             fieldList[j].value = value;
  617.                         }
  618.                     } else if (fieldTag == 'select') {
  619.                         var idx = 0;
  620.                         var optionElem = fieldList[j].firstChild;
  621.                         while (optionElem &&
  622.                                optionElem.text != value &&
  623.                                optionElem.value != value)
  624.                         {
  625.                             optionElem = optionElem.nextSibling;
  626.                             idx++;
  627.                         }
  628.                         if ((idx < fieldList[j].length) && (idx > -1)) {
  629.                             this.debug(' * MATCH!! setting SELECT value: '+value);
  630.                             fieldList[j].selectedIndex = idx;
  631.                         }
  632.                     }
  633.                 }
  634.             }
  635.         }
  636.         // Recurse on iframes
  637.         var iframeList = doc.getElementsByTagName('iframe');
  638.         for (var i = 0; i < iframeList.length; i++) {
  639.             this.DoAutoFillGecko(iframeList.item(i).contentDocument, fieldValues);
  640.         }
  641.         // Recurse on frames
  642.         var frameList = doc.getElementsByTagName('frame');
  643.         for (var i = 0; i < frameList.length; i++) {
  644.             this.DoAutoFillGecko(frameList.item(i).contentDocument, fieldValues);
  645.         }
  646.     },
  647.  
  648.  
  649.     DoAutoFillAndSubmit : function(uri, datacard, fieldValues) {
  650.         this.debug('DoAutoFillAndSubmit('+datacard+')');
  651.         this.DoAutoFill(uri, datacard, fieldValues, true);
  652.     },
  653.  
  654.  
  655.     DoSubmitTrident : function(hpDoc, fieldValues)
  656.     {
  657.         if (!hpDoc)    return;
  658.         this.debug('DoSubmitTrident()');
  659.         var bDone = false;
  660.         var i = 0;
  661.         while (!bDone && i < fieldValues.fields.length) {
  662.             var old = fieldValues.preModifiedValues.queryElementAt(
  663.                         i, Components.interfaces.nsIPrefLocalizedString).toString();
  664.             var notOld = fieldValues.htmlValues.queryElementAt(
  665.                         i, Components.interfaces.nsIPrefLocalizedString).toString();
  666.             this.debug('--> oldValue: ' + old + ', newValue: ' + notOld);
  667.             if ((old != notOld) &&
  668.                     (old.length > 0 || notOld.length > 0)) {
  669.                 bDone = true;
  670.             }    else {
  671.                 i++;
  672.             }
  673.         }
  674.         
  675.         var field;
  676.         if (bDone) {
  677.             this.CheckForSavePrompt(gBrowser.currentURI);
  678.             field = fieldValues.fields[i];
  679.         } else {
  680.             field = fieldValues.fields.join(this.FIELD_DELIMITER);
  681.         }
  682.         
  683.         hpDoc.datacardSubmitForm(field);
  684.     },
  685.  
  686.  
  687.     // Recurses through a Gecko doc and submits the first form it comes to
  688.     // that actually has autofilled fields.  Returns a boolean indicating
  689.     // whether the form has been submitted
  690.     DoSubmitGecko : function(doc, fieldValues) {
  691.         if (!doc) return;
  692.         this.debug('DoSubmitGecko()');
  693.         var forms = doc.getElementsByTagName('form');
  694.         // Submit the first form we find that actually has autofilled fields
  695.         for (var i = 0; i < forms.length; i++) {
  696.             var inputFields = this.EnumerateInputFieldsGecko(forms[i]);
  697.             for (var j = 0; j < inputFields.length; j++) {
  698.                 var field = inputFields[j];
  699.                 var fieldName = field.getAttribute('name');
  700.                 var k;
  701.                 for (k = 0; k < fieldValues.fields.length; k++) {
  702.                     if (fieldValues.fields[k] == fieldName) break;
  703.                 }
  704.                 if (k >= fieldValues.fields.length)
  705.                     continue;
  706.                 var old = fieldValues.preModifiedValues.queryElementAt(
  707.                             k, Components.interfaces.nsIPrefLocalizedString);
  708.                 var notOld = fieldValues.htmlValues.queryElementAt(
  709.                             k, Components.interfaces.nsIPrefLocalizedString);
  710.                 this.debug('--> oldValue: ' + old.toString() + ', newValue: ' + notOld.toString());
  711.                 if ((old.toString() != notOld.toString())
  712.                      && field.value && field.value.length) {
  713.                     forms[i].submit();
  714.                     return true;
  715.                 }
  716.             }
  717.         }
  718.         // Recurse on iframes
  719.         var iframeList = doc.getElementsByTagName('iframe');
  720.         for (var i = 0; i < iframeList.length; i++) {
  721.             if (this.DoSubmitGecko(iframeList.item(i).contentDocument, fieldValues))
  722.                 return true;
  723.         }
  724.         // Recurse on frames
  725.         var frameList = doc.getElementsByTagName('frame');
  726.         for (var i = 0; i < frameList.length; i++) {
  727.             if (this.DoSubmitGecko(frameList.item(i).contentDocument, fieldValues))
  728.                 return true;
  729.         }
  730.         return false;
  731.     },
  732.     
  733.     
  734.     DatacardsExist : function(checkForEmptyDatacard) {
  735.         this.debug('DatacardsExist()');
  736.  
  737.         // If there's no datacards folder, then there are no datacards
  738.         var folder = this.GetDatacardsFolder();
  739.         if (!folder.exists()) return false;
  740.  
  741.         if (checkForEmptyDatacard) {
  742.             var datacardList = this.GetDatacardList();
  743.             if (datacardList.length == 1) {        // only one datacard
  744.                 var datacardFile = this.GetDatacardFileByName(datacardList[0]);
  745.                 if (this.IsEmptyDatacard(datacardFile))
  746.                 {
  747.                     this.debug('Found an empty datacard!');
  748.                     return false;                        // if the datacard is empty, then let's consider it as if we don't have any datacards yet
  749.                 }
  750.             }
  751.         }
  752.  
  753.         // If the datacards folder contains ANY files at all...
  754.         this.debug('Looking for any number of datacards...');
  755.         var entries = folder.directoryEntries;
  756.         return entries.hasMoreElements();
  757.     },
  758.  
  759.  
  760.     // checks to see if the datacard is 'empty' (with the exception of the country field)
  761.     IsEmptyDatacard : function(datacardFile) {
  762.         var regularDict = this.afService.GetDatacardFieldsByType(
  763.                                            datacardFile,
  764.                                            nsIAutoFillService.FIELDTYPE_REGULAR);
  765.         var keys = regularDict.getKeys({});
  766.         for (var k in keys) {
  767.             var key = keys[k];
  768.             this.debug('IsEmptyDatacard() - key is '+key);
  769.             // can't check country because we always default to a value in the drop-down,
  770.             // avoid checking the country field (among other fields...)
  771.             if (key == 'country' || key == 'alt_country' || key == 'cc_type' ||
  772.                 key == 'cc_expire_month' || key == 'cc_expire_year')
  773.                 continue;
  774.  
  775.             if (this.GetStringFromDict(regularDict, key).length > 0)
  776.                 return false;
  777.         }
  778.  
  779.         var advancedDict = this.afService.GetDatacardFieldsByType(
  780.                                             datacardFile,
  781.                                             nsIAutoFillService.FIELDTYPE_ADVANCED);
  782.         keys = advancedDict.getKeys({});
  783.         for (var k in keys) {
  784.             if (this.GetStringFromDict(advancedDict, keys[k]).length > 0)
  785.                 return false;
  786.         }
  787.  
  788.         return true;
  789.     },
  790.  
  791.  
  792.     DatacardExists : function(datacard) {
  793.         this.debug('DatacardExists('+datacard+')');
  794.         var file = this.GetDatacardFileByName(datacard);
  795.         return file.exists();
  796.     },
  797.  
  798.  
  799.     // Returns an array of all the INPUT and SELECT fields
  800.     // found in the document, in order of occurence
  801.     EnumerateInputFieldsGecko : function(elm, arr) {
  802.         if (!arr) arr = new Array();
  803.         var tagName = (elm.tagName)
  804.                     ? elm.tagName.toLowerCase()
  805.                     : '';
  806.         if ((tagName == 'input') || (tagName == 'select')) {
  807.             if (!elm.hasAttribute('name')) return arr;
  808.             if (tagName == 'input') {
  809.                 if (elm.hasAttribute('type')) {
  810.                     var type = elm.getAttribute('type').toLowerCase();
  811.                     if ( !((type == 'text') ||
  812.                            (type == 'checkbox')) )
  813.                     {
  814.                         return arr;
  815.                     }
  816.                 }
  817.             }
  818.             arr[arr.length] = elm;
  819.             return arr;
  820.         } else {
  821.             // Recurse
  822.             for (var i = 0; i < elm.childNodes.length; i++) {
  823.                 arr = this.EnumerateInputFieldsGecko(elm.childNodes[i], arr);
  824.             }
  825.         }
  826.         return arr;
  827.     },
  828.  
  829.  
  830.     // Returns true iff the rippedValues represent a change to
  831.     // the datacard fields that would need to be saved
  832.     FieldValuesMatchDatacard : function(datacard, uri, rippedValues) {
  833.         this.debug('FieldValuesMatchDatacard(..., '+datacard+')');
  834.         // Get the *current* values out of the datacard
  835.         var datacardFile = this.GetDatacardFileByName(datacard);
  836.         var regularDict = this.afService.GetDatacardFieldsByType(
  837.                                            datacardFile,
  838.                                            nsIAutoFillService.FIELDTYPE_REGULAR);
  839.         var advancedDict = this.afService.GetDatacardFieldsByType(
  840.                                             datacardFile,
  841.                                             nsIAutoFillService.FIELDTYPE_ADVANCED);
  842.         // Combine 'regulardict' and 'advancedDict' together into one dict
  843.         var oldDict = regularDict;
  844.         var keys = advancedDict.getKeys({});
  845.         for (var k in keys) {
  846.             var key = keys[k];
  847.             oldDict.setValue(key, advancedDict.getValue(key));
  848.         }
  849.         // Calculate what the new values would be if we were to save
  850.         var values = this.ToPrefLocalizedStringArray(rippedValues.values);
  851.         var newDict = this.FillDatacardFieldsFromHTML(
  852.                                        datacardFile,
  853.                                        uri.asciiHost,
  854.                                        rippedValues.fields.join(this.FIELD_DELIMITER),
  855.                                        values,
  856.                                        false);  // don't save
  857.         this.debug(' - comparing oldDict to newDict...');
  858.         // Compare newDict to oldDict
  859.         // Only return true if each dictionary's keys/values match
  860.         newKeys = newDict.getKeys({});
  861.         this.debug('  - got '+newKeys.length+' keys');
  862.         for (var k in newKeys) {
  863.             var key = newKeys[k];
  864.             this.debug('  - key '+k+' = '+key);
  865.             var oldVal = '';
  866.             try {
  867.                 if (oldDict.hasKey(key))
  868.                 {
  869.                     oldVal = oldDict.getValue(key);
  870.                     oldVal.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
  871.                     oldVal = oldVal.toString();
  872.                 }
  873.             } catch (ex) { }
  874.             var newVal = '';
  875.             try {
  876.                 if (newDict.hasKey(key))
  877.                 {
  878.                     newVal = newDict.getValue(key);
  879.                     newVal.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
  880.                     newVal = newVal.toString();
  881.                 }
  882.             } catch (ex) { }
  883.             if ((newVal != '') && (newVal != oldVal)) {
  884.                 this.debug('   - FOUND a DIFFERENCE:  old = "'+oldVal+'"  new = "'+newVal+'"');
  885.                 return false;
  886.             }
  887.         }
  888.         this.debug('  - NO diff');
  889.         return true;
  890.     },
  891.  
  892.  
  893.     // This function gets called when a form is submitted resulting in
  894.     // potential changes to a datacard.  It determines whether to prompt the
  895.     // user and saves the changes, as appropriate.
  896.     CheckForSavePrompt : function(uri, topElement) {
  897.         this.debug('CheckForSavePrompt()');
  898.  
  899.         // First, see if we should just ignore changes for this specific site
  900.         //if (this.IsSiteOnIgnoreChangesList(uri)) return;
  901.         if (this.IsSiteBlacklisted(uri)) return;
  902.  
  903.         // See if we should save changes, in general
  904.         var ps = this.prefService;
  905.  
  906.         // Exit if pref is not int type
  907.         if (ps.getPrefType("datacard.autosave") != ps.PREF_INT)
  908.             return;
  909.  
  910.         // The "datacard.autosave" pref is set in Options dialog->
  911.         // Form Fill panel->Datacards tab->Preferences tab->dropdown menu
  912.         // See datacard-datapanels.inc
  913.  
  914.         // If "do nothing" is selected then exit
  915.         if (ps.getIntPref("datacard.autosave") == 2)
  916.             return;
  917.  
  918.         // If "automatically save" is selected (==1), keep going...
  919.  
  920.         // Check if the user wants a prompt to save
  921.         var doPrompt = (ps.getIntPref("datacard.autosave") == 0);
  922.  
  923.         // Get the current fieldnames and values from the page
  924.         var rippedValues = this.RipFields(topElement);
  925.         
  926.         // Bail if the page is not autofillable
  927.         if (!this.IsPageAutoFillable(uri, rippedValues)) return;
  928.  
  929.         if (this.DatacardsExist(true)) {
  930.             // If the values entered don't match the datacard associated with
  931.             // this site, we prompt user to update it
  932.             var datacard = this.FindDatacardForURI(uri);
  933.             if (datacard == undefined) return;
  934.             if (!this.FieldValuesMatchDatacard(datacard, uri, rippedValues)) {
  935.                 if (doPrompt) {
  936.                     // The user wants to be prompted before saving
  937.                     window.openDialog(
  938.                         "chrome://mozapps/content/autofill/datacardUpdateDialog.xul",
  939.                         "_blank", "chrome,modal,resizable=no", rippedValues);
  940.                 } else {
  941.                     // The user doesn't want to be prompted
  942.                     this.SetDatacardValues(datacard, uri, rippedValues);
  943.                     this.SetDatacardForHost(uri.host, datacard);
  944.                 }
  945.             }
  946.         } else {
  947.             // No datacards yet exist.  What we want to do now is determine
  948.             // whether the user has in fact entered any data that is worthy
  949.             // of saving in a datacard.  If so, we will prompt the user to
  950.             // save it, otherwise we won't waste their time.
  951.             
  952.             this.CreateDatacardTemp(this.TEMP_DATACARD_FILENAME);
  953.             var tempFile = this.GetDatacardFileByName(this.TEMP_DATACARD_FILENAME);
  954.             var values = this.ToPrefLocalizedStringArray(rippedValues.values);
  955.             this.FillDatacardFieldsFromHTML(
  956.                                            tempFile,
  957.                                            uri.asciiHost,
  958.                                            rippedValues.fields.join(this.FIELD_DELIMITER),
  959.                                            values,
  960.                                            true);  // save (temporarily)
  961.             var regDict = this.afService.GetDatacardFieldsByType(
  962.                                            tempFile,
  963.                                            nsIAutoFillService.FIELDTYPE_REGULAR);
  964.             this.DeleteDatacard(this.TEMP_DATACARD_FILENAME);
  965.             
  966.             // regFields now contains the datacard regular values that would be
  967.             // saved if we were to create a real datacard, so see if any data is
  968.             // actually there
  969.             var foundValue = false;
  970.             var keys = regDict.getKeys({});
  971.             for (var k in keys) {
  972.                 var key = keys[k];
  973.                 var value = regDict.getValue(key);
  974.                 if (value.toString() != '') {
  975.                     foundValue = true;
  976.                     break;
  977.                 }
  978.             }
  979.  
  980.             if (foundValue) {
  981.                 // There is a value to save, so prompt.
  982.                 // (even if the user has the datacard.autosave.prompt pref set to true,
  983.                 // we still need to prompt here because we need a new datacard name)
  984.                 window.openDialog(
  985.                     "chrome://mozapps/content/autofill/datacardSaveNewDialog.xul",
  986.                     "_blank", "chrome,modal,resizable=no", rippedValues);
  987.             }
  988.         }
  989.     },
  990.  
  991.  
  992.     // Returns true if there is a fillable form.
  993.     // The 'checkData' boolean argument indicates whether we should additionally
  994.     // check whether we have data for the fields actually present.
  995.     IsPageAutoFillable : function(uri, fieldValues, checkData) {
  996.         this.debug('IsPageAutoFillable('+uri.spec+', ..., checkData:'+checkData+')');
  997.  
  998.         if (!checkData) checkData = false;
  999.         
  1000.         if (!(uri.schemeIs('http') ||
  1001.               uri.schemeIs('https') ||
  1002.               uri.schemeIs('ftp')))
  1003.         {
  1004.             // Not autofillable :-P
  1005.             return;
  1006.         }
  1007.  
  1008.         // If the page has a password field, it will be handled
  1009.         // by Passcard instead
  1010.         if (CheckIsPasscardable(true)) return false;
  1011.  
  1012.         // Find out how many potentially autofillable fields there are
  1013.         var numAFfields =
  1014.             this.afService.NumAutoFillFields(
  1015.                 uri.asciiHost,
  1016.                 fieldValues.fields.join(this.FIELD_DELIMITER));
  1017.  
  1018.         // Any page with fewer than 4 fillable fields is NOT considered
  1019.         if (numAFfields < 4) return false;
  1020.  
  1021.         // If we don't have to check data, then we are done.  There are fields.
  1022.         if (!checkData) return true;
  1023.  
  1024.         // From here on, we only want to consider these fields 'fillable' if
  1025.         // there is actually data to put in them.
  1026.         var datacard = this.FindDatacardForURI(uri);
  1027.         var datacardFile = this.GetDatacardFileByName(datacard);
  1028.         var values = this.InitPrefLocalizedStringArray(fieldValues.values.length);
  1029.         //this.debugPLStringArray(values);
  1030.         this.afService.FillHTMLFieldsFromDatacard(datacardFile,
  1031.                                                   uri.asciiHost,
  1032.                                                   fieldValues.fields.join(this.FIELD_DELIMITER),
  1033.                                                   values);
  1034.         this.debugPLStringArray(values);
  1035.         var e = values.enumerate();
  1036.         var i = 0;
  1037.         fieldValues.newValues = new Array();
  1038.         var fillCount = 0;
  1039.         var lastMatchedField = null;
  1040.         while (e.hasMoreElements())
  1041.         {
  1042.             var value = e.getNext();
  1043.             value.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
  1044.             value = value.toString();
  1045.             fieldValues.newValues[i] = value;
  1046.             if (value != '') {
  1047.                 lastMatchedField = fieldValues.fields[i];
  1048.                 this.debug('  - there is an HTML field ['+fieldValues.fields[i]
  1049.                     +'] fillable with value ['+value+']');
  1050.                 fillCount++;
  1051.             }
  1052.             i++;
  1053.         }
  1054.  
  1055.         if (fillCount >= 2) return true;
  1056.  
  1057.         // This is to avoid the problem where the search field on netscape.com,
  1058.         // which is named "name" was getting autofilled
  1059.         if ((fillCount == 1) && (lastMatchedField != 'name')) return true;
  1060.  
  1061.         // Fallback
  1062.         return false;
  1063.     },
  1064.  
  1065.  
  1066.     DoHighlight : function(fieldValues) {
  1067.         this.debug('DoHighlight()');
  1068.         var ps = this.prefService;
  1069.         if (ps.getPrefType('datacard.highlight') &&
  1070.             !ps.getBoolPref('datacard.highlight'))
  1071.         {
  1072.             return; // Should not highlight
  1073.         }
  1074.         // Check if Trident
  1075.         var hpDoc = getTridentDocument();
  1076.         if (hpDoc) {
  1077.             try {
  1078.                 // Highlight Trident
  1079.                 this.DoHighlightTrident(hpDoc, fieldValues);
  1080.             } catch (ex) {
  1081.                 this.debug(' ***** Error highlighting on Trident!!!');
  1082.             }
  1083.         } else {
  1084.             // Highlight Gecko
  1085.             this.DoHighlightGecko(gBrowser.contentDocument, fieldValues);
  1086.         }
  1087.     },
  1088.  
  1089.  
  1090.     // recursive highlighting of form fields throughout subframes
  1091.     DoHighlightTrident : function(doc, fieldValues) {
  1092.         if (!doc) return;
  1093.         this.debug('DoHighlightTrident()');
  1094.         var fields = "";
  1095.         for (var j = 0; j < fieldValues.fields.length; j++) {
  1096.             var value = fieldValues.newValues[j];
  1097.             if (value && value.length > 0)
  1098.             {
  1099.                 if (fields.length > 0)
  1100.                     fields += this.FIELD_DELIMITER;
  1101.                 fields += fieldValues.fields[j];
  1102.             }
  1103.         }
  1104.         this.debug('--> Fields to highlight in Trident: ' + fields);
  1105.         doc.datacardHighlight(fields);
  1106.     },
  1107.     
  1108.     
  1109.     // recursive highlighting of form fields throughout subframes
  1110.     DoHighlightGecko : function(doc, fieldValues) {
  1111.         if (!doc) return;
  1112.         this.debug('DoHighlightGecko()');
  1113.         var fieldList = this.EnumerateInputFieldsGecko(doc);
  1114.         for (var i = 0; i < fieldList.length; i++) {
  1115.             var field = fieldList[i];
  1116.             var fieldName = field.getAttribute('name');
  1117.             //this.debug(' - checking: '+fieldName);
  1118.             for (var j = 0; j < fieldValues.fields.length; j++) {
  1119.                 if (fieldValues.fields[j] != fieldName) continue;
  1120.                 if (fieldValues.newValues[j] == undefined) continue;
  1121.                 if (fieldValues.newValues[j] == '') continue;
  1122.                 this.debug('  - highlighting ['+fieldValues.fields[j]
  1123.                     +'] because we have a value ['+fieldValues.newValues[j]+']');
  1124.                 field.style.backgroundColor = this.FIELD_HIGHLIGHT_COLOR;
  1125.                 break;
  1126.             }
  1127.         }
  1128.         // Recurse on iframes
  1129.         var iframeList = doc.getElementsByTagName('iframe');
  1130.         for (var i = 0; i < iframeList.length; i++) {
  1131.             this.DoHighlightGecko(iframeList.item(i).contentDocument, fieldValues);
  1132.         }
  1133.         // Recurse on frames
  1134.         var frameList = doc.getElementsByTagName('frame');
  1135.         for (var i = 0; i < frameList.length; i++) {
  1136.             this.DoHighlightGecko(frameList.item(i).contentDocument, fieldValues);
  1137.         }
  1138.     },
  1139.  
  1140.  
  1141.     // Here is where we decide whether to show the message bar, automatically
  1142.     // fill the page, fill and submit, or do nothing
  1143.     DoPageLoadedCheck : function() {
  1144.         this.debug('DoPageLoadedCheck()');
  1145.  
  1146.         // remove the message bar in case we return early (we will redraw the bar if necessary)
  1147.         // dump('--> gCurrentNotificationBar: ' + gCurrentNotificationBar + '\n');
  1148.         if (gCurrentNotificationBar == 'datacard') {
  1149.             this.debug('--> Hiding Datacard Messagebar');
  1150.             gBrowser.hideMessage(gBrowser.selectedBrowser, "top");
  1151.         }
  1152.  
  1153.         // Bail if the page has a password field.  It will be handled
  1154.         // by Passcard instead
  1155.         if (CheckIsPasscardable(true)) return;
  1156.         this.debug('--> Not a Passcard');
  1157.  
  1158.         // Bail if the site is blacklisted
  1159.         if (this.IsSiteBlacklisted(gBrowser.currentURI)) return;
  1160.         this.debug('--> Site is not blacklisted');
  1161.  
  1162.         // Bail if there is no datacard to use
  1163.         var datacard = this.FindDatacardForURI(gBrowser.currentURI);
  1164.         if (!datacard) return;
  1165.         this.debug('--> There is a Datacard to use');
  1166.  
  1167.         // Get the current fieldnames and values from the page
  1168.         var fieldValues = this.RipFields();
  1169.  
  1170.         // Bail if the page is not autofillable (assumes datacard is loaded)
  1171.         var fillable = this.IsPageAutoFillable(gBrowser.currentURI, fieldValues, true);
  1172.         if (!fillable) {
  1173.             this.debug('--> Page is NOT datacardable');
  1174.             return;
  1175.         }
  1176.         this.debug('--> Page is datacardable');
  1177.  
  1178.         // See if we should highlight
  1179.         var ps = datacardUtils.prefService;
  1180.         if (ps.getPrefType('datacard.highlight') &&
  1181.             ps.getBoolPref('datacard.highlight'))
  1182.         {
  1183.             this.DoHighlight(fieldValues);
  1184.         }
  1185.         this.debug('--> Finished datacard highlighting');
  1186.  
  1187.         var datacardFile = this.GetDatacardFileByName(datacard);
  1188.         var datacardProps = this.afService.GetDatacardFieldsByType(
  1189.                                              datacardFile,
  1190.                                              nsIAutoFillService.FIELDTYPE_PROPERTY);
  1191.  
  1192.         var whenToFill = this.GetStringFromDict(datacardProps, 'WhenToFill');
  1193.         switch (whenToFill) {
  1194.         case 'displayprompt':
  1195.             // Show the browser message bar
  1196.             displayNotificationBar("datacard");
  1197.             break;
  1198.         case 'filldatacard':
  1199.             this.DoAutoFill(gBrowser.currentURI, datacard, fieldValues);
  1200.             break;
  1201.         case 'fillsubmitdatacard':
  1202.             this.DoAutoFillAndSubmit(gBrowser.currentURI, datacard, fieldValues);
  1203.             break;
  1204.         case 'donothing':
  1205.         default:
  1206.             // Do nothing :)
  1207.         }
  1208.     },
  1209.  
  1210.  
  1211.     GetStringFromDict : function(dict, key) {
  1212.         dict.QueryInterface(Components.interfaces.nsIDictionary);
  1213.         var value = dict.getValue(key);
  1214.         value.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
  1215.         return value.toString();
  1216.     },
  1217.  
  1218.  
  1219.     CreatePrefLocalizedString : function(str) {
  1220.         if (!str) str = '';
  1221.         var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
  1222.                             .createInstance(Components.interfaces.nsIPrefLocalizedString);
  1223.         pls.setDataWithLength(str.length, str);
  1224.         return pls;
  1225.     },
  1226.  
  1227.  
  1228.     InitPrefLocalizedStringArray : function(size) {
  1229.         var blankArray = new Array();
  1230.         for (var i = 0; i < size; i++) {
  1231.             blankArray[i] = '';
  1232.         }
  1233.         return this.ToPrefLocalizedStringArray(blankArray);
  1234.     },
  1235.  
  1236.  
  1237.     ToPrefLocalizedStringArray : function(arr) {
  1238.         var newArr = Components.classes["@mozilla.org/array;1"]
  1239.                                .createInstance(Components.interfaces.nsIMutableArray);
  1240.         for (var i = 0; i < arr.length; i++)
  1241.         {
  1242.             newArr.appendElement(
  1243.                 this.CreatePrefLocalizedString(arr[i]), false);
  1244.         }
  1245.         return newArr;
  1246.     },
  1247.   
  1248.   
  1249.     // This gets called from nsHTMLPluginDocument (C++) when a datacard form
  1250.     // has been submitted in Trident.  The dictionary contains only those
  1251.     // values that have actually changed
  1252.     TridentSubmit : function() {
  1253.         this.debug('TridentSubmit()');
  1254.  
  1255.         // TODO: We should figure out the URL of the page on which the form
  1256.         // was submitted, and check if this should actually be ignored
  1257.         var uri = gBrowser.currentURI;
  1258.         this.CheckForSavePrompt(uri, null);
  1259.     },
  1260.  
  1261.  
  1262.     SaveDatacard : function() {
  1263.         this.debug('SaveDatacard()');
  1264.         var hpDoc = getTridentDocument();
  1265.         if (hpDoc)        
  1266.             this.CheckForSavePrompt(gBrowser.currentURI);
  1267.         else
  1268.             this.CheckForSavePrompt(gBrowser.currentURI, gBrowser.contentDocument);
  1269.     },
  1270.  
  1271.  
  1272.     // Retrieves a sequence of field-value pairs for the current
  1273.     // document.  The return value is an object with a 'fields'
  1274.     // array and a corresponding 'values' array.  If the 'elt'
  1275.     // param is provided (Gecko only) it will restrict the searching
  1276.     // to a particular dom node.
  1277.     RipFields : function(elt) {
  1278.         this.debug('RipFields()');
  1279.         var fieldValues = new Object();
  1280.         fieldValues.fields = new Array();
  1281.         fieldValues.values = new Array();
  1282.         var hpDoc = getTridentDocument();
  1283.         if (hpDoc) {
  1284.             // Trident
  1285.             var combinedFieldValues = hpDoc.RipFieldsTrident();
  1286.             if (combinedFieldValues) {
  1287.                 combinedFieldValues = combinedFieldValues.split(this.RULE_SEPARATOR);
  1288.                 fieldValues.fields = combinedFieldValues[0].split(this.FIELD_DELIMITER);
  1289.                 fieldValues.values = combinedFieldValues[1].split(this.FIELD_DELIMITER);
  1290.                 // The field values get URL encoded inside Trident, so unencode here
  1291.                 for (var i = 0; i < fieldValues.values.length; i++) {
  1292.                     fieldValues.values[i] = unescape(fieldValues.values[i]);
  1293.                 }
  1294.             }
  1295.         } else {
  1296.             // Gecko
  1297.             this.RipFieldsGecko(elt, fieldValues);
  1298.         }
  1299.         this.debug('fieldValues:');
  1300.         this.debug(' .fields: '+fieldValues.fields);
  1301.         this.debug(' .values: '+fieldValues.values);
  1302.         return fieldValues;
  1303.     },
  1304.  
  1305.  
  1306.     RipFieldsGecko : function(elt, fieldValues) {
  1307.         this.debug('RipFieldsGecko()');
  1308.         var parentElt = elt;
  1309.         if (!elt) parentElt = gBrowser.contentDocument;
  1310.         var fieldList = this.EnumerateInputFieldsGecko(parentElt);
  1311.         for (var i = 0; i < fieldList.length; i++) {
  1312.             var fieldIdx = fieldValues.fields.length;
  1313.             var name = fieldList[i].getAttribute('name');
  1314.             var val = fieldList[i].value;
  1315.             this.debug(' ripped['+fieldIdx+']: '+name+' = '+val);
  1316.             fieldValues.fields[fieldIdx] = name;
  1317.             fieldValues.values[fieldIdx] = val;
  1318.         }
  1319.         // Recurse on iframes
  1320.         var iframeList = parentElt.getElementsByTagName('iframe');
  1321.         for (var i = 0; i < iframeList.length; i++) {
  1322.             this.RipFieldsGecko(iframeList.item(i).contentDocument, fieldValues);
  1323.         }
  1324.         // Recurse on frames
  1325.         var frameList = parentElt.getElementsByTagName('frame');
  1326.         for (var i = 0; i < frameList.length; i++) {
  1327.             this.RipFieldsGecko(frameList.item(i).contentDocument, fieldValues);
  1328.         }
  1329.     },
  1330.  
  1331.  
  1332.     debug : function(msg) {
  1333.         if (this.DEBUG)
  1334.             dump('datacardUtils: '+msg+'\n');
  1335.     },
  1336.  
  1337.  
  1338.     debugPLStringArray : function(plStringArray) {
  1339.         var e = plStringArray.enumerate();
  1340.         var i = 0;
  1341.         while (e.hasMoreElements()) {
  1342.             var item = e.getNext();
  1343.             item.QueryInterface(Components.interfaces.nsIPrefLocalizedString);
  1344.             this.debug(' ['+i+']: '+item.toString());
  1345.             i++;
  1346.         }
  1347.     }
  1348. };
  1349.  
  1350.  
  1351. // This is the observer for form submissions.  It gets registered with the
  1352. // Observer Service to watch for the "formsubmit" topic.  But unfortunately
  1353. // the implementation of nsHTMLFormElement was only notifying observers who
  1354. // implemented nsIFormSubmitObserver, and I couldn't get that working in
  1355. // JavaScript.  So instead, I ended up hacking nsHTMLFormElement to fall back
  1356. // to notifying nsIObservers as well.
  1357. function datacardSubmitObserver() {
  1358. }
  1359. datacardSubmitObserver.prototype = {
  1360.  
  1361.     QueryInterface : function(aIID) {
  1362.         this.debug('QueryInterface('+aIID+')');
  1363.         if (aIID.equals(Components.interfaces.nsIObserver) ||
  1364.             aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
  1365.             aIID.equals(Components.interfaces.nsIWeakReference) ||
  1366.             aIID.equals(Components.interfaces.nsISupports))
  1367.         {
  1368.             return this;
  1369.         }
  1370.         throw Components.results.NS_NOINTERFACE;
  1371.     },
  1372.  
  1373.     QueryReferent : function(aIID) {
  1374.         this.debug('QueryReferent('+aIID+')');
  1375.         return this;
  1376.     },
  1377.  
  1378.     observe : function(formElement, topic, url) {
  1379.         //try {
  1380.             this.debug('observe(formElement:'+formElement+' topic:'+topic+' url:'+url+')');
  1381.  
  1382.             // A form has been submitted, so determine whether we want to save
  1383.             // changes to the datacard, ignore changes, or prompt the user
  1384.  
  1385.             // First, get the URI of the page on which the form was submitted
  1386.             var uri = datacardUtils.ioService.newURI(url, null, null);
  1387.             datacardUtils.CheckForSavePrompt(uri, formElement);
  1388.  
  1389.         //} catch (ex) {
  1390.         //    dump('Caught Exception in datacardUtils.js: datacardSubmitObserver.observe()\n');
  1391.         //    for (var prop in ex)
  1392.         //        dump(' '+prop+': '+ex[prop]+'\n');
  1393.         //}
  1394.     },
  1395.  
  1396.     debug : function(msg) {
  1397.         datacardUtils.debug('datacardSubmitObserver: '+msg);
  1398.     }
  1399. };
  1400.  
  1401.  
  1402. // This gets called from nsHTMLPluginDocument (C++) when a datacard form
  1403. // has been submitted in Trident.  The dictionary contains only those values
  1404. // that have actually changed
  1405. function TridentSubmit() {
  1406.     datacardUtils.TridentSubmit();
  1407. }
  1408.  
  1409.  
  1410. datacardUtils.Init();