home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2005 August / PCADVD_121.iso / internet / nsb-setup.exe / chrome / toolkit.jar / content / passwordmgr / passwordManager.js < prev    next >
Encoding:
JavaScript  |  2005-04-22  |  20.6 KB  |  666 lines

  1.  
  2. /*** =================== INITIALISATION CODE =================== ***/
  3.  
  4. var kObserverService;
  5. var kSignonBundle;
  6. var gSelectUserInUse = false;
  7.  
  8. // interface variables
  9. var passwordmanager     = null;
  10.  
  11. // password-manager lists
  12. /* MERC JVL - moved to passcard.js
  13. var signons             = [];
  14. var rejects             = [];
  15. var deletedSignons      = [];
  16. var deletedRejects      = [];
  17. */
  18.  
  19. var showingPasswords = false;
  20.  
  21. function pmStartup() {
  22.   // xpconnect to password manager interfaces
  23.   passwordmanager = Components.classes["@mozilla.org/passwordmanager;1"].getService(Components.interfaces.nsIPasswordManager);
  24.  
  25.   kSignonBundle = document.getElementById("signonBundle");
  26.  
  27.   // be prepared to reload the display if anything changes
  28.   kObserverService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  29.   kObserverService.addObserver(signonReloadDisplay, "signonChanged", false);
  30.  
  31.   // be prepared to disable the buttons when selectuser dialog is in use
  32.   kObserverService.addObserver(signonReloadDisplay, "signonSelectUser", false);
  33.  
  34.   signonsTree = document.getElementById("signonsTree");
  35.   rejectsTree = document.getElementById("rejectsTree");
  36.  
  37.   // set initial password-manager tab
  38. // MERC JVL  var tabBox = document.getElementById("tabbox");
  39. // MERC JVL  tabBox.selectedTab = document.getElementById("signonsTab");
  40.  
  41.   // label the show/hide password button and the close button
  42. // MERC JVL  document.getElementById("togglePasswords").label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
  43. // MERC JVL  document.documentElement.getButton("accept").label = kSignonBundle.getString("close");
  44.  
  45.   // load password manager items
  46.   if (!LoadSignons()) {
  47.     return; /* user failed to unlock the database */
  48.   }
  49.   LoadRejects();
  50. }
  51.  
  52. function Shutdown() {
  53.   kObserverService.removeObserver(signonReloadDisplay, "signonChanged");
  54.   kObserverService.removeObserver(signonReloadDisplay, "signonSelectUser");
  55. }
  56.  
  57. var signonReloadDisplay = {
  58.   observe: function(subject, topic, state) {
  59.     if (topic == "signonChanged") {
  60.       if (state == "signons") {
  61.         parent.passcardFinalize.signons.length = 0;        // MERC JVL
  62.         if (lastSignonSortColumn == "host") {
  63.           lastSignonSortAscending = !lastSignonSortAscending; // prevents sort from being reversed
  64.         }
  65.         LoadSignons();
  66.       } else if (state == "rejects") {
  67.         parent.passcardFinalize.rejects.length = 0;        // MERC JVL
  68.         if (lastRejectSortColumn == "host") {
  69.           lastRejectSortAscending = !lastRejectSortAscending; // prevents sort from being reversed
  70.         }
  71.         LoadRejects();
  72.       }
  73.     } else if (topic == "signonSelectUser") {
  74.       if (state == "suspend") {
  75.         gSelectUserInUse = true;
  76.         document.getElementById("removeSignon").disabled = true;
  77.         document.getElementById("removeAllSignons").disabled = true;
  78. // MERC JVL        document.getElementById("togglePasswords").disabled = true;
  79.       } else if (state == "resume") {
  80.         gSelectUserInUse = false;
  81.         var selections = GetTreeSelections(signonsTree);
  82.         if (selections.length > 0) {
  83.           document.getElementById("removeSignon").disabled = false;
  84.         }
  85.         if (parent.passcardFinalize.signons.length > 0) {    // MERC JVL
  86.           document.getElementById("removeAllSignons").disabled = false;
  87. // MERC JVL          document.getElementById("togglePasswords").disabled = false;
  88.         }
  89.       } else if (state == "inUse") {
  90.         gSelectUserInUse = true;
  91.       }
  92.     }
  93.   }
  94. }
  95.  
  96. /*** =================== SAVED SIGNONS CODE =================== ***/
  97.  
  98. var signonsTreeView = {
  99.   rowCount : 0,
  100.   setTree : function(tree) {},
  101.   getImageSrc : function(row,column) {},
  102.   getProgressMode : function(row,column) {},
  103.   getCellValue : function(row,column) {},
  104.   getCellText : function(row,column) {
  105.     var rv="";
  106.     // MERC JVL BEGIN
  107.     /*
  108.     if (column=="siteCol") {
  109.       rv = signons[row].host;
  110.     } else if (column=="userCol") {
  111.       rv = signons[row].user;
  112.     } else if (column=="passwordCol") {
  113.       rv = signons[row].password;
  114.     }
  115.     */
  116.     try
  117.     {
  118.       if (column=="siteCol") {
  119.         rv = parent.passcardFinalize.signons[row].host;            // MERC JVL
  120.       } else if (column=="passcardCol") {
  121.         rv = parent.passcardFinalize.signons[row].passcard;        // MERC JVL
  122.       }
  123.     }
  124.     catch(e)
  125.     {
  126. // TODO JVL : Why is there an exception?
  127. //      dump("***** Error in passwordManager.js, signonsTreeView:getCellText() - no signon? *****\n");
  128.     }
  129.     // MERC END
  130.     return rv;
  131.   },
  132.   isSeparator : function(index) { return false; },
  133.   isSorted : function() { return false; },
  134.   isContainer : function(index) { return false; },
  135.   cycleHeader : function(aColId, aElt) {},
  136.   getRowProperties : function(row,column,prop) {},
  137.   getColumnProperties : function(column,columnElement,prop) {},
  138.   getCellProperties : function(row,prop) {}
  139.  };
  140. var signonsTree;
  141.  
  142. function Signon(number, host, passcard, user, rawuser, password, autologin,        // MERC JVL
  143.                                 protect, lastUsed, uniqueID, advancedFields) {                                // MERC JVL
  144.   this.number = number;
  145.   this.host = host;
  146.   this.user = user;
  147.   this.rawuser = rawuser;
  148.   this.password = password;
  149.     // MERC JVL BEGIN
  150.   this.passcard = passcard;
  151.   this.autologin = autologin;
  152.   this.protect = protect;
  153.   this.lastUsed = lastUsed;
  154.   this.uniqueID = uniqueID;
  155.   this.advancedFields = advancedFields;
  156.     // MERC END
  157. }
  158.  
  159. function LoadSignons() {
  160.     // ensure there is a Default Passcard                            // MERC JVL
  161.   passwordmanager.AddDefaultPasscard(true, true);        // MERC JVL
  162.  
  163.   // loads signons into table
  164.   var enumerator = passwordmanager.enumerator;
  165.   var count = 0;
  166.   var bIsIncorrectMasterPassword = false;        // MERC JVL
  167.  
  168.   while (enumerator.hasMoreElements()) {
  169.     var nextPassword;
  170.     try {
  171.       nextPassword = enumerator.getNext();
  172.     } catch(e) {
  173.       /* user supplied invalid database key */
  174.       window.close();
  175.       return false;
  176.     }
  177.     nextPassword = nextPassword.QueryInterface(Components.interfaces.nsIPassword);
  178.     var passcard = nextPassword.passcard;                                            // MERC JVL
  179.     var host = nextPassword.host;
  180.     var user;
  181.     var password;
  182.  
  183.     // MERC JVL BEGIN    
  184.     if (bIsIncorrectMasterPassword && nextPassword.protect != 0)
  185.       continue;
  186.     // MERC END
  187.  
  188.     // try/catch in case decryption fails (invalid signon entry)
  189.     try {
  190.       user = nextPassword.user;
  191.       password = nextPassword.password;
  192.     } catch (e) {
  193.       // hide this entry
  194.       dump("could not decrypt user/password for host " + host + "\n");
  195.       bIsIncorrectMasterPassword = true;        // MERC JVL
  196.       continue;
  197.     }
  198.     var rawuser = user;
  199.     // MERC JVL BEGIN
  200.     var autologin = nextPassword.autologin;
  201.     var protect = nextPassword.protect;
  202.     var lastUsed = nextPassword.lastUsed;
  203.     var uniqueID = nextPassword.uniqueID;
  204.     var advancedFields = nextPassword.advancedFields;
  205.     // MERC END
  206.  
  207.     // if no username supplied, try to parse it out of the url
  208.     if (user == "") {
  209.       var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  210.                                 .getService(Components.interfaces.nsIIOService);
  211.       try {
  212.         user = ioService.newURI(host, null, null).username;
  213.         if (user == "") {
  214. //          user = "<>";        // MERC JVL
  215.           user = "";                // MERC JVL
  216.         }
  217.       } catch(e) {
  218. //        user = "<>";            // MERC JVL
  219.         user = "";                    // MERC JVL
  220.       }
  221.     }
  222.  
  223.     parent.passcardFinalize.signons[count] = new Signon(count++, host, passcard, user, rawuser, password, autologin,    // MERC JVL
  224.                                                                                                                 protect, lastUsed, uniqueID, advancedFields);                                    // MERC JVL
  225.   }
  226.   signonsTreeView.rowCount = parent.passcardFinalize.signons.length;        // MERC JVL
  227.  
  228.   // sort and display the table
  229.   signonsTree.treeBoxObject.view = signonsTreeView;
  230.   SignonColumnSort('host');
  231.   if (signonsTreeView.rowCount > 0)
  232.       signonsTreeView.selection.select(0);        // MERC JVL - select the first entry in the list
  233.  
  234.   // disable "remove all signons" button if there are no signons
  235.   var element = document.getElementById("removeAllSignons");
  236. // MERC JVL  var toggle = document.getElementById("togglePasswords");
  237.   if (parent.passcardFinalize.signons.length == 0 || gSelectUserInUse) {    // MERC JVL
  238.     element.setAttribute("disabled","true");
  239. // MERC JVL    toggle.setAttribute("disabled","true");
  240.   } else {
  241.     element.removeAttribute("disabled");
  242. // MERC JVL    toggle.removeAttribute("disabled");
  243.   }
  244.  
  245.   return true;
  246. }
  247.  
  248. function SignonSelected() {
  249.   var selections = GetTreeSelections(signonsTree);
  250.   if (selections.length && !gSelectUserInUse) {
  251.     document.getElementById("removeSignon").removeAttribute("disabled");
  252.   }
  253. }
  254.  
  255. function DeleteSignon() {
  256.   DeleteSelectedItemFromTree(signonsTree, signonsTreeView,
  257.                              signons, deletedSignons,
  258.                              "removeSignon", "removeAllSignons");
  259.   FinalizeSignonDeletions();
  260. }
  261.  
  262. function DeleteAllSignons(bRefreshList) {
  263.   DeleteAllFromTree(signonsTree, signonsTreeView,
  264.                     parent.passcardFinalize.signons,                    // MERC JVL
  265.                     parent.passcardFinalize.deletedSignons,        // MERC JVL
  266.                     "removeSignon", "removeAllSignons");
  267.   FinalizeSignonDeletions();
  268.   
  269.   if (bRefreshList)            // MERC JVL
  270.       LoadSignons();            // MERC JVL
  271. }
  272.  
  273. // MERC JVL
  274. function DeleteEncryptedSignons(bRefreshList) {
  275.   DeleteProtectedFromTree(signonsTree, signonsTreeView,
  276.                           parent.passcardFinalize.signons,
  277.                           parent.passcardFinalize.deletedSignons,
  278.                           "removeSignon", "removeAllSignons");
  279.   FinalizeSignonDeletions();
  280.   
  281.   if (bRefreshList)            // MERC JVL
  282.       LoadSignons();            // MERC JVL
  283. }
  284.  
  285. function TogglePasswordVisible() {
  286.     return;     // MERC JVL
  287.  
  288.   if (!showingPasswords && !ConfirmShowPasswords())
  289.     return;
  290.  
  291.   showingPasswords = !showingPasswords;
  292.   document.getElementById("togglePasswords").label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
  293.   document.getElementById("passwordCol").hidden = !showingPasswords;
  294. }
  295.  
  296. function AskUserShowPasswords() {
  297.   var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
  298.   var dummy = { value: false };
  299.  
  300.   // Confirm the user wants to display passwords
  301.   return prompter.confirmEx(window,
  302.           null,
  303.           kSignonBundle.getString("noMasterPasswordPrompt"),
  304.           prompter.BUTTON_TITLE_YES * prompter.BUTTON_POS_0 + prompter.BUTTON_TITLE_NO * prompter.BUTTON_POS_1,
  305.           null, null, null, null, dummy) == 0;    // 0=="Yes" button
  306. }
  307.  
  308. function ConfirmShowPasswords() {
  309.   // This doesn't harm if passwords are not encrypted
  310.   var tokendb = Components.classes["@mozilla.org/security/pk11tokendb;1"]
  311.                     .createInstance(Components.interfaces.nsIPK11TokenDB);
  312.   var token = tokendb.getInternalKeyToken();
  313.  
  314.   // If there is no master password, still give the user a chance to opt-out of displaying passwords
  315.   if (token.checkPassword(""))
  316.     return AskUserShowPasswords();
  317.  
  318.   // So there's a master password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl).
  319.   try {
  320.     // Relogin and ask for the master password.
  321.     token.login(true);  // 'true' means always prompt for token password. User will be prompted until
  322.                         // clicking 'Cancel' or entering the correct password.
  323.   } catch (e) {
  324.     // An exception will be thrown if the user cancels the login prompt dialog.
  325.     // User is also logged out of Software Security Device.
  326.   }
  327.  
  328.   return token.isLoggedIn();
  329. }
  330.  
  331. function FinalizeSignonDeletions() {
  332.   var deletedSignons = parent.passcardFinalize.deletedSignons;        // MERC JVL
  333.   for (var s=0; s<deletedSignons.length; s++) {
  334.     passwordmanager.removeUser(deletedSignons[s].host, deletedSignons[s].rawuser);
  335.   }
  336.   deletedSignons.length = 0;
  337. }
  338.  
  339. function HandleSignonKeyPress(e) {
  340.   if (e.keyCode == 46) {
  341.     DeleteSignonSelected();
  342.   }
  343. }
  344.  
  345. var lastSignonSortColumn = "";
  346. var lastSignonSortAscending = false;
  347.  
  348. function SignonColumnSort(column) {
  349.   lastSignonSortAscending =
  350.     SortTree(signonsTree, signonsTreeView, parent.passcardFinalize.signons,        // MERC JVL
  351.                  column, lastSignonSortColumn, lastSignonSortAscending);
  352.   lastSignonSortColumn = column;
  353. }
  354.  
  355. /*** =================== REJECTED SIGNONS CODE =================== ***/
  356.  
  357. var rejectsTreeView = {
  358.   rowCount : 0,
  359.   setTree : function(tree){},
  360.   getImageSrc : function(row,column) {},
  361.   getProgressMode : function(row,column) {},
  362.   getCellValue : function(row,column) {},
  363.   getCellText : function(row,column){
  364.     var rv="";
  365.     if (column=="rejectCol") {
  366.       rv = parent.passcardFinalize.rejects[row].host;        // MERC JVL
  367.     }
  368.     return rv;
  369.   },
  370.   isSeparator : function(index) {return false;},
  371.   isSorted: function() { return false; },
  372.   isContainer : function(index) {return false;},
  373.   cycleHeader : function(aColId, aElt) {},
  374.   getRowProperties : function(row,column,prop){},
  375.   getColumnProperties : function(column,columnElement,prop){},
  376.   getCellProperties : function(row,prop){}
  377.  };
  378. var rejectsTree;
  379.  
  380. function Reject(number, host) {
  381.   this.number = number;
  382.   this.host = host;
  383. }
  384.  
  385. function LoadRejects() {
  386.   var enumerator = passwordmanager.rejectEnumerator;
  387.   var count = 0;
  388.   while (enumerator.hasMoreElements()) {
  389.     var nextReject = enumerator.getNext();
  390.     nextReject = nextReject.QueryInterface(Components.interfaces.nsIPassword);
  391.     var host = nextReject.host;
  392.     parent.passcardFinalize.rejects[count] = new Reject(count++, host);        // MERC JVL
  393.   }
  394.   rejectsTreeView.rowCount = parent.passcardFinalize.rejects.length;        // MERC JVL
  395.  
  396.   // sort and display the table
  397.   rejectsTree.treeBoxObject.view = rejectsTreeView;
  398.   RejectColumnSort('host');
  399.  
  400.   var element = document.getElementById("removeAllSignons");    // MERC JVL - only using one set of Remove/RemoveAll buttons
  401.   if (parent.passcardFinalize.rejects.length == 0) {            // MERC JVL
  402.     element.setAttribute("disabled","true");
  403.   } else {
  404.     element.removeAttribute("disabled");
  405.   }
  406. }
  407.  
  408. function RejectSelected() {
  409.   var selections = GetTreeSelections(rejectsTree);
  410.   if (selections.length) {
  411.     document.getElementById("removeReject").removeAttribute("disabled");
  412.   }
  413. }
  414.  
  415. function DeleteReject() {
  416.   DeleteSelectedItemFromTree(rejectsTree, rejectsTreeView,
  417.                                  rejects, deletedRejects,
  418.                                  "removeReject", "removeAllRejects");
  419.   FinalizeRejectDeletions();
  420. }
  421.  
  422. function DeleteAllRejects() {
  423.   DeleteAllFromTree(rejectsTree, rejectsTreeView,
  424.                         rejects, deletedRejects,
  425.                         "removeReject", "removeAllRejects");
  426.   FinalizeRejectDeletions();
  427. }
  428.  
  429. function FinalizeRejectDeletions() {
  430.   for (var r=0; r<deletedRejects.length; r++) {
  431.     passwordmanager.removeReject(deletedRejects[r].host);
  432.   }
  433.   deletedRejects.length = 0;
  434. }
  435.  
  436. function HandleRejectKeyPress(e) {
  437.   if (e.keyCode == 46) {
  438.     DeleteRejectSelected();
  439.   }
  440. }
  441.  
  442. var lastRejectSortColumn = "";
  443. var lastRejectSortAscending = false;
  444.  
  445. function RejectColumnSort(column) {
  446.   lastRejectSortAscending =
  447.     SortTree(rejectsTree, rejectsTreeView, parent.passcardFinalize.rejects,        // MERC JVL
  448.                  column, lastRejectSortColumn, lastRejectSortAscending);
  449.   lastRejectSortColumn = column;
  450. }
  451.  
  452. /*** =================== GENERAL CODE =================== ***/
  453.  
  454. // Remove whitespace from both ends of a string
  455. function TrimString(string)
  456. {
  457.   if (!string) {
  458.     return "";
  459.   }
  460.   return string.replace(/(^\s+)|(\s+$)/g, '')
  461. }
  462.  
  463. // MERC JVL
  464. function DeleteProtectedFromTree
  465.     (tree, view, table, deletedTable, removeButton, removeAllButton)
  466. {
  467.     var bIsDefaultProtected = false;
  468.     
  469.   // remove all protected items from table and place in deleted table
  470.   var deleteCount = 0;
  471.   for (var i = 0; i < table.length; i++) {
  472.       if (table[i].protect == 1) {
  473.             deletedTable[deletedTable.length] = table[i];
  474.             deleteCount++;
  475.  
  476.             if (table[i].host == "DefaultPasscard")
  477.                 bIsDefaultProtected = true;
  478.         }
  479.   }
  480.   table.length -= deleteCount;
  481.  
  482.   // update the tree view and notify the tree
  483.   view.rowCount = table.length;
  484.  
  485.   var box = tree.treeBoxObject;
  486.   box.rowCountChanged(0, -deletedTable.length);
  487.   box.invalidate();
  488.  
  489.   // disable buttons
  490.   if (table.length == 0 || (table.length == 1 && !bIsDefaultProtected))
  491.   {
  492.     document.getElementById(removeButton).setAttribute("disabled", "true")
  493.     document.getElementById(removeAllButton).setAttribute("disabled","true");
  494.   }
  495. }
  496.  
  497. function DeleteAllFromTree
  498.     (tree, view, table, deletedTable, removeButton, removeAllButton) {
  499.  
  500.   // remove all items from table and place in deleted table
  501.   for (var i=0; i<table.length; i++) {
  502.     deletedTable[deletedTable.length] = table[i];
  503.   }
  504.   table.length = 0;
  505.  
  506.   // clear out selections
  507.   view.selection.select(-1); 
  508.  
  509.   // update the tree view and notify the tree
  510.   view.rowCount = 0;
  511.  
  512.   var box = tree.treeBoxObject;
  513.   box.rowCountChanged(0, -deletedTable.length);
  514.   box.invalidate();
  515.  
  516.  
  517.   // disable buttons
  518.   document.getElementById(removeButton).setAttribute("disabled", "true")
  519.   document.getElementById(removeAllButton).setAttribute("disabled","true");
  520. }
  521.  
  522. function DeleteSelectedItemFromTree
  523.     (tree, view, table, deletedTable, removeButton, removeAllButton) {
  524.  
  525.   var box = tree.treeBoxObject;
  526.  
  527.   // Remove selected items from list (by setting them to null) and place in
  528.   // deleted list.  At the same time, notify the tree of the row count changes.
  529.  
  530.   var selection = box.selection;
  531.   var oldSelectStart = table.length;
  532.   box.beginUpdateBatch();
  533.  
  534.   var selCount = selection.getRangeCount();
  535.   var min = new Object();
  536.   var max = new Object();
  537.  
  538.   for (var s = 0; s < selCount; ++s) {
  539.     selection.getRangeAt(s, min, max);
  540.     var minVal = min.value;
  541.     var maxVal = max.value;
  542.  
  543.     oldSelectStart = minVal < oldSelectStart ? minVal : oldSelectStart;
  544.  
  545.     var rowCount = maxVal - minVal + 1;
  546.     view.rowCount -= rowCount;
  547.     box.rowCountChanged(minVal, -rowCount);
  548.  
  549.     for (var i = minVal; i <= maxVal; ++i) {
  550.       deletedTable[deletedTable.length] = table[i];
  551.       table[i] = null;
  552.     }
  553.   }
  554.  
  555.   // collapse list by removing all the null entries
  556.   for (var j = 0; j < table.length; ++j) {
  557.     if (!table[j]) {
  558.       var k = j;
  559.       while (k < table.length && !table[k])
  560.         k++;
  561.  
  562.       table.splice(j, k-j);
  563.     }
  564.   }
  565.  
  566.   box.endUpdateBatch();
  567.  
  568.   // update selection and/or buttons
  569.   var removeButton1 = document.getElementById(removeButton);
  570.   var removeAllButton1 = document.getElementById(removeAllButton);
  571.  
  572.   if (table.length) {
  573.     removeButton1.removeAttribute("disabled");
  574.     removeAllButton1.removeAttribute("disabled");
  575.  
  576.     selection.select(oldSelectStart < table.length ? oldSelectStart : table.length - 1);
  577.   } else {
  578.     removeButton1.setAttribute("disabled", "true");
  579.     removeAllButton1.setAttribute("disabled", "true");
  580.   }
  581. }
  582.  
  583. function GetTreeSelections(tree) {
  584.   var selections = [];
  585.   var select = tree.treeBoxObject.selection;
  586.   if (select) {
  587.     var count = select.getRangeCount();
  588.     var min = new Object();
  589.     var max = new Object();
  590.     for (var i=0; i<count; i++) {
  591.       select.getRangeAt(i, min, max);
  592.       for (var k=min.value; k<=max.value; k++) {
  593.         if (k != -1) {
  594.           selections[selections.length] = k;
  595.         }
  596.       }
  597.     }
  598.   }
  599.   return selections;
  600. }
  601.  
  602. function SortTree(tree, view, table, column, lastSortColumn, lastSortAscending, updateSelection) {
  603.  
  604.   // remember which item was selected so we can restore it after the sort
  605.   var selections = GetTreeSelections(tree);
  606.   var selectedNumber = selections.length ? table[selections[0]].number : -1;
  607.  
  608.   // determine if sort is to be ascending or descending
  609.   var ascending = (column == lastSortColumn) ? !lastSortAscending : true;
  610.  
  611.   // do the sort
  612.   var compareFunc;
  613.   if (ascending) {
  614.     compareFunc = function compare(first, second) {
  615.       return CompareLowerCase(first[column], second[column]);
  616.     }
  617.   } else {
  618.     compareFunc = function compare(first, second) {
  619.       return CompareLowerCase(second[column], first[column]);
  620.     }
  621.   }
  622.   table.sort(compareFunc);
  623.  
  624.   // restore the selection
  625.   var selectedRow = -1;
  626.   if (selectedNumber>=0 && updateSelection) {
  627.     for (var s=0; s<table.length; s++) {
  628.       if (table[s].number == selectedNumber) {
  629.         // update selection
  630.         // note: we need to deselect before reselecting in order to trigger ...Selected()
  631.         tree.treeBoxObject.view.selection.select(-1);
  632.         tree.treeBoxObject.view.selection.select(s);
  633.         selectedRow = s;
  634.         break;
  635.       }
  636.     }
  637.   }
  638.  
  639.   // display the results
  640.   tree.treeBoxObject.invalidate();
  641.   if (selectedRow >= 0) {
  642.     tree.treeBoxObject.ensureRowIsVisible(selectedRow)
  643.   }
  644.  
  645.   return ascending;
  646. }
  647.  
  648. /**
  649.  * Case insensitive string comparator.
  650.  */
  651. function CompareLowerCase(first, second) {
  652.  
  653.   var firstLower  = first.toLowerCase();
  654.   var secondLower = second.toLowerCase();
  655.  
  656.   if (firstLower < secondLower) {
  657.     return -1;
  658.   }
  659.  
  660.   if (firstLower > secondLower) {
  661.     return 1;
  662.   }
  663.  
  664.   return 0;
  665. }
  666.