Bug 998081 - Show login metadata in password manager UI. r=mattn
authorJustin Dolske <dolske@mozilla.com>
Thu, 22 May 2014 12:18:36 -0700
changeset 184451 074c77fc983bd61c90cfbcbe3899284cdb1fc0f7
parent 184450 198035dfa405cfcbc03a79f739c5387900e5b212
child 184452 480860e8a7f2f24db685819f68be003e980d9149
push id6930
push userjdolske@mozilla.com
push dateThu, 22 May 2014 19:18:45 +0000
treeherderfx-team@074c77fc983b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattn
bugs998081
milestone32.0a1
Bug 998081 - Show login metadata in password manager UI. r=mattn
toolkit/components/passwordmgr/content/passwordManager.js
toolkit/components/passwordmgr/content/passwordManager.xul
toolkit/components/passwordmgr/content/passwordManagerCommon.js
toolkit/locales/en-US/chrome/passwordmgr/passwordManager.dtd
--- a/toolkit/components/passwordmgr/content/passwordManager.js
+++ b/toolkit/components/passwordmgr/content/passwordManager.js
@@ -3,16 +3,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*** =================== SAVED SIGNONS CODE =================== ***/
 
 var kSignonBundle;
 var showingPasswords = false;
+var dateFormatter = new Intl.DateTimeFormat(undefined,
+                      { day: "numeric", month: "short", year: "numeric" });
+var dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
+                             { day: "numeric", month: "short", year: "numeric",
+                               hour: "numeric", minute: "numeric" });
 
 function SignonsStartup() {
   kSignonBundle = document.getElementById("signonBundle");
   document.getElementById("togglePasswords").label = kSignonBundle.getString("showPasswords");
   document.getElementById("togglePasswords").accessKey = kSignonBundle.getString("showPasswordsAccessKey");
   document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsSpielAll");
   LoadSignons();
 
@@ -36,26 +41,38 @@ var signonsTreeView = {
   selection: null,
 
   rowCount : 0,
   setTree : function(tree) {},
   getImageSrc : function(row,column) {},
   getProgressMode : function(row,column) {},
   getCellValue : function(row,column) {},
   getCellText : function(row,column) {
+    var time;
     var signon = this._filterSet.length ? this._filterSet[row] : signons[row];
     switch (column.id) {
       case "siteCol":
         return signon.httpRealm ?
                (signon.hostname + " (" + signon.httpRealm + ")"):
                signon.hostname;
       case "userCol":
         return signon.username || "";
       case "passwordCol":
         return signon.password || "";
+      case "timeCreatedCol":
+        time = new Date(signon.timeCreated);
+        return dateFormatter.format(time);
+      case "timeLastUsedCol":
+        time = new Date(signon.timeLastUsed);
+        return dateAndTimeFormatter.format(time);
+      case "timePasswordChangedCol":
+        time = new Date(signon.timePasswordChanged);
+        return dateFormatter.format(time);
+      case "timesUsedCol":
+        return signon.timesUsed;
       default:
         return "";
     }
   },
   isSeparator : function(index) { return false; },
   isSorted : function() { return false; },
   isContainer : function(index) { return false; },
   cycleHeader : function(column) {},
@@ -72,16 +89,17 @@ var signonsTreeView = {
 
 function LoadSignons() {
   // loads signons into table
   try {
     signons = passwordmanager.getAllLogins();
   } catch (e) {
     signons = [];
   }
+  signons.forEach(login => login.QueryInterface(Components.interfaces.nsILoginMetaInfo));
   signonsTreeView.rowCount = signons.length;
 
   // sort and display the table
   signonsTree.treeBoxObject.view = signonsTreeView;
   // The sort column didn't change. SortTree (called by
   // SignonColumnSort) assumes we want to toggle the sort
   // direction but here we don't so we have to trick it
   lastSignonSortAscending = !lastSignonSortAscending;
@@ -192,16 +210,24 @@ function HandleSignonKeyPress(e) {
 function getColumnByName(column) {
   switch (column) {
     case "hostname":
       return document.getElementById("siteCol");
     case "username":
       return document.getElementById("userCol");
     case "password":
       return document.getElementById("passwordCol");
+    case "timeCreated":
+      return document.getElementById("timeCreatedCol");
+    case "timeLastUsed":
+      return document.getElementById("timeLastUsedCol");
+    case "timePasswordChanged":
+      return document.getElementById("timePasswordChangedCol");
+    case "timesUsed":
+      return document.getElementById("timesUsedCol");
   }
 }
 
 var lastSignonSortColumn = "hostname";
 var lastSignonSortAscending = true;
 
 function SignonColumnSort(column) {
   // clear out the sortDirection attribute on the old column
--- a/toolkit/components/passwordmgr/content/passwordManager.xul
+++ b/toolkit/components/passwordmgr/content/passwordManager.xul
@@ -49,31 +49,51 @@
       <label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
       <textbox id="filter" flex="1" type="search"
                aria-controls="signonsTree"
                oncommand="_filterPasswords();"/>
     </hbox>
 
     <label control="signonsTree" id="signonsIntro"/>
     <separator class="thin"/>
-    <tree id="signonsTree" flex="1" style="height: 20em;" hidecolumnpicker="true"
+    <tree id="signonsTree" flex="1"
+          width="750"
+          style="height: 20em;"
           onkeypress="HandleSignonKeyPress(event)"
           onselect="SignonSelected();"
           context="signonsTreeContextMenu">
       <treecols>
-        <treecol id="siteCol" label="&treehead.site.label;" flex="5"
+        <treecol id="siteCol" label="&treehead.site.label;" flex="40"
                  onclick="SignonColumnSort('hostname');" persist="width"
+                 ignoreincolumnpicker="true"
                  sortDirection="ascending"/>
         <splitter class="tree-splitter"/>
-        <treecol id="userCol" label="&treehead.username.label;" flex="2"
+        <treecol id="userCol" label="&treehead.username.label;" flex="25"
+                 ignoreincolumnpicker="true"
                  onclick="SignonColumnSort('username');" persist="width"/>
         <splitter class="tree-splitter"/>
-        <treecol id="passwordCol" label="&treehead.password.label;" flex="2"
+        <treecol id="passwordCol" label="&treehead.password.label;" flex="15"
+                 ignoreincolumnpicker="true"
                  onclick="SignonColumnSort('password');" persist="width"
                  hidden="true"/>
+        <splitter class="tree-splitter"/>
+        <treecol id="timeCreatedCol" label="&treehead.timeCreated.label;" flex="10"
+                 onclick="SignonColumnSort('timeCreated');" persist="width hidden"
+                 hidden="true"/>
+        <splitter class="tree-splitter"/>
+        <treecol id="timeLastUsedCol" label="&treehead.timeLastUsed.label;" flex="20"
+                 onclick="SignonColumnSort('timeLastUsed');" persist="width hidden"/>
+        <splitter class="tree-splitter"/>
+        <treecol id="timePasswordChangedCol" label="&treehead.timePasswordChanged.label;" flex="10"
+                 onclick="SignonColumnSort('timePasswordChanged');" persist="width hidden"/>
+        <splitter class="tree-splitter"/>
+        <treecol id="timesUsedCol" label="&treehead.timesUsed.label;" flex="1"
+                 onclick="SignonColumnSort('timesUsed');" persist="width hidden"
+                 hidden="true"/>
+        <splitter class="tree-splitter"/>
       </treecols>
       <treechildren/>
     </tree>
     <separator class="thin"/>
     <hbox id="SignonViewerButtons">
       <button id="removeSignon" disabled="true" icon="remove"
               label="&remove.label;" accesskey="&remove.accesskey;"
               oncommand="DeleteSignon();"/>
--- a/toolkit/components/passwordmgr/content/passwordManagerCommon.js
+++ b/toolkit/components/passwordmgr/content/passwordManagerCommon.js
@@ -164,28 +164,50 @@ function SortTree(tree, view, table, col
 
   // remember which item was selected so we can restore it after the sort
   var selections = GetTreeSelections(tree);
   var selectedNumber = selections.length ? table[selections[0]].number : -1;
 
   // determine if sort is to be ascending or descending
   var ascending = (column == lastSortColumn) ? !lastSortAscending : true;
 
-  // do the sort
-  var compareFunc;
-  if (ascending) {
-    compareFunc = function compare(first, second) {
-      return CompareLowerCase(first[column], second[column]);
+  function compareFunc(a, b) {
+    var valA, valB;
+    switch (column) {
+      case "hostname":
+        var realmA = a.httpRealm;
+        var realmB = b.httpRealm;
+        realmA = realmA == null ? "" : realmA.toLowerCase();
+        realmB = realmB == null ? "" : realmB.toLowerCase();
+
+        valA = a[column].toLowerCase() + realmA;
+        valB = b[column].toLowerCase() + realmB;
+        break;
+      case "username":
+      case "password":
+        valA = a[column].toLowerCase();
+        valB = b[column].toLowerCase();
+        break;
+
+      default:
+        valA = a[column];
+        valB = b[column];
     }
-  } else {
-    compareFunc = function compare(first, second) {
-      return CompareLowerCase(second[column], first[column]);
-    }
+
+    if (valA < valB)
+      return -1;
+    if (valA > valB)
+      return 1;
+    return 0;
   }
+
+  // do the sort
   table.sort(compareFunc);
+  if (!ascending)
+    table.reverse();
 
   // restore the selection
   var selectedRow = -1;
   if (selectedNumber>=0 && updateSelection) {
     for (var s=0; s<table.length; s++) {
       if (table[s].number == selectedNumber) {
         // update selection
         // note: we need to deselect before reselecting in order to trigger ...Selected()
@@ -201,33 +223,8 @@ function SortTree(tree, view, table, col
   tree.treeBoxObject.invalidate();
   if (selectedRow >= 0) {
     tree.treeBoxObject.ensureRowIsVisible(selectedRow)
   }
 
   return ascending;
 }
 
-/**
- * Case insensitive string comparator.
- */
-function CompareLowerCase(first, second) {
-  var firstLower, secondLower;
-
-  // Are we sorting nsILoginInfo entries or just strings?
-  if (first.hostname) {
-    firstLower  = first.hostname.toLowerCase();
-    secondLower = second.hostname.toLowerCase();
-  } else {
-    firstLower  = first.toLowerCase();
-    secondLower = second.toLowerCase();
-  }
-
-  if (firstLower < secondLower) {
-    return -1;
-  }
-
-  if (firstLower > secondLower) {
-    return 1;
-  }
-
-  return 0;
-}
--- a/toolkit/locales/en-US/chrome/passwordmgr/passwordManager.dtd
+++ b/toolkit/locales/en-US/chrome/passwordmgr/passwordManager.dtd
@@ -7,16 +7,21 @@
 <!ENTITY      closebutton.label               "Close">
 <!ENTITY      closebutton.accesskey           "C">
 
 <!ENTITY      loginsSpielExceptions.label     "Passwords for the following sites will not be saved:">
 
 <!ENTITY      treehead.site.label             "Site">
 <!ENTITY      treehead.username.label         "Username">
 <!ENTITY      treehead.password.label         "Password">
+<!ENTITY      treehead.timeCreated.label         "First Used">
+<!ENTITY      treehead.timeLastUsed.label        "Last Used">
+<!ENTITY      treehead.timePasswordChanged.label "Last Changed">
+<!ENTITY      treehead.timesUsed.label           "Times Used">
+
 <!ENTITY      remove.label                    "Remove">
 <!ENTITY      remove.accesskey                "R">
 <!ENTITY      removeall.label                 "Remove All">
 <!ENTITY      removeall.accesskey             "A">
 
 <!ENTITY      filter.label                    "Search:">
 <!ENTITY      filter.accesskey                "S">