Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | // -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | /*** =================== SAVED SIGNONS CODE =================== ***/ |
michael@0 | 8 | |
michael@0 | 9 | var kSignonBundle; |
michael@0 | 10 | var showingPasswords = false; |
michael@0 | 11 | |
michael@0 | 12 | function SignonsStartup() { |
michael@0 | 13 | kSignonBundle = document.getElementById("signonBundle"); |
michael@0 | 14 | document.getElementById("togglePasswords").label = kSignonBundle.getString("showPasswords"); |
michael@0 | 15 | document.getElementById("togglePasswords").accessKey = kSignonBundle.getString("showPasswordsAccessKey"); |
michael@0 | 16 | document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsSpielAll"); |
michael@0 | 17 | LoadSignons(); |
michael@0 | 18 | |
michael@0 | 19 | // filter the table if requested by caller |
michael@0 | 20 | if (window.arguments && |
michael@0 | 21 | window.arguments[0] && |
michael@0 | 22 | window.arguments[0].filterString) |
michael@0 | 23 | setFilter(window.arguments[0].filterString); |
michael@0 | 24 | |
michael@0 | 25 | FocusFilterBox(); |
michael@0 | 26 | } |
michael@0 | 27 | |
michael@0 | 28 | function setFilter(aFilterString) { |
michael@0 | 29 | document.getElementById("filter").value = aFilterString; |
michael@0 | 30 | _filterPasswords(); |
michael@0 | 31 | } |
michael@0 | 32 | |
michael@0 | 33 | var signonsTreeView = { |
michael@0 | 34 | _filterSet : [], |
michael@0 | 35 | _lastSelectedRanges : [], |
michael@0 | 36 | selection: null, |
michael@0 | 37 | |
michael@0 | 38 | rowCount : 0, |
michael@0 | 39 | setTree : function(tree) {}, |
michael@0 | 40 | getImageSrc : function(row,column) {}, |
michael@0 | 41 | getProgressMode : function(row,column) {}, |
michael@0 | 42 | getCellValue : function(row,column) {}, |
michael@0 | 43 | getCellText : function(row,column) { |
michael@0 | 44 | var signon = this._filterSet.length ? this._filterSet[row] : signons[row]; |
michael@0 | 45 | switch (column.id) { |
michael@0 | 46 | case "siteCol": |
michael@0 | 47 | return signon.httpRealm ? |
michael@0 | 48 | (signon.hostname + " (" + signon.httpRealm + ")"): |
michael@0 | 49 | signon.hostname; |
michael@0 | 50 | case "userCol": |
michael@0 | 51 | return signon.username || ""; |
michael@0 | 52 | case "passwordCol": |
michael@0 | 53 | return signon.password || ""; |
michael@0 | 54 | default: |
michael@0 | 55 | return ""; |
michael@0 | 56 | } |
michael@0 | 57 | }, |
michael@0 | 58 | isSeparator : function(index) { return false; }, |
michael@0 | 59 | isSorted : function() { return false; }, |
michael@0 | 60 | isContainer : function(index) { return false; }, |
michael@0 | 61 | cycleHeader : function(column) {}, |
michael@0 | 62 | getRowProperties : function(row) { return ""; }, |
michael@0 | 63 | getColumnProperties : function(column) { return ""; }, |
michael@0 | 64 | getCellProperties : function(row,column) { |
michael@0 | 65 | if (column.element.getAttribute("id") == "siteCol") |
michael@0 | 66 | return "ltr"; |
michael@0 | 67 | |
michael@0 | 68 | return ""; |
michael@0 | 69 | } |
michael@0 | 70 | }; |
michael@0 | 71 | |
michael@0 | 72 | |
michael@0 | 73 | function LoadSignons() { |
michael@0 | 74 | // loads signons into table |
michael@0 | 75 | try { |
michael@0 | 76 | signons = passwordmanager.getAllLogins(); |
michael@0 | 77 | } catch (e) { |
michael@0 | 78 | signons = []; |
michael@0 | 79 | } |
michael@0 | 80 | signonsTreeView.rowCount = signons.length; |
michael@0 | 81 | |
michael@0 | 82 | // sort and display the table |
michael@0 | 83 | signonsTree.treeBoxObject.view = signonsTreeView; |
michael@0 | 84 | // The sort column didn't change. SortTree (called by |
michael@0 | 85 | // SignonColumnSort) assumes we want to toggle the sort |
michael@0 | 86 | // direction but here we don't so we have to trick it |
michael@0 | 87 | lastSignonSortAscending = !lastSignonSortAscending; |
michael@0 | 88 | SignonColumnSort(lastSignonSortColumn); |
michael@0 | 89 | |
michael@0 | 90 | // disable "remove all signons" button if there are no signons |
michael@0 | 91 | var element = document.getElementById("removeAllSignons"); |
michael@0 | 92 | var toggle = document.getElementById("togglePasswords"); |
michael@0 | 93 | if (signons.length == 0) { |
michael@0 | 94 | element.setAttribute("disabled","true"); |
michael@0 | 95 | toggle.setAttribute("disabled","true"); |
michael@0 | 96 | } else { |
michael@0 | 97 | element.removeAttribute("disabled"); |
michael@0 | 98 | toggle.removeAttribute("disabled"); |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | return true; |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | function SignonSelected() { |
michael@0 | 105 | var selections = GetTreeSelections(signonsTree); |
michael@0 | 106 | if (selections.length) { |
michael@0 | 107 | document.getElementById("removeSignon").removeAttribute("disabled"); |
michael@0 | 108 | } |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | function DeleteSignon() { |
michael@0 | 112 | var syncNeeded = (signonsTreeView._filterSet.length != 0); |
michael@0 | 113 | DeleteSelectedItemFromTree(signonsTree, signonsTreeView, |
michael@0 | 114 | signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons, |
michael@0 | 115 | deletedSignons, "removeSignon", "removeAllSignons"); |
michael@0 | 116 | FinalizeSignonDeletions(syncNeeded); |
michael@0 | 117 | } |
michael@0 | 118 | |
michael@0 | 119 | function DeleteAllSignons() { |
michael@0 | 120 | var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] |
michael@0 | 121 | .getService(Components.interfaces.nsIPromptService); |
michael@0 | 122 | |
michael@0 | 123 | // Confirm the user wants to remove all passwords |
michael@0 | 124 | var dummy = { value: false }; |
michael@0 | 125 | if (prompter.confirmEx(window, |
michael@0 | 126 | kSignonBundle.getString("removeAllPasswordsTitle"), |
michael@0 | 127 | kSignonBundle.getString("removeAllPasswordsPrompt"), |
michael@0 | 128 | prompter.STD_YES_NO_BUTTONS + prompter.BUTTON_POS_1_DEFAULT, |
michael@0 | 129 | null, null, null, null, dummy) == 1) // 1 == "No" button |
michael@0 | 130 | return; |
michael@0 | 131 | |
michael@0 | 132 | var syncNeeded = (signonsTreeView._filterSet.length != 0); |
michael@0 | 133 | DeleteAllFromTree(signonsTree, signonsTreeView, |
michael@0 | 134 | signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons, |
michael@0 | 135 | deletedSignons, "removeSignon", "removeAllSignons"); |
michael@0 | 136 | FinalizeSignonDeletions(syncNeeded); |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | function TogglePasswordVisible() { |
michael@0 | 140 | if (showingPasswords || masterPasswordLogin(AskUserShowPasswords)) { |
michael@0 | 141 | showingPasswords = !showingPasswords; |
michael@0 | 142 | document.getElementById("togglePasswords").label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords"); |
michael@0 | 143 | document.getElementById("togglePasswords").accessKey = kSignonBundle.getString(showingPasswords ? "hidePasswordsAccessKey" : "showPasswordsAccessKey"); |
michael@0 | 144 | document.getElementById("passwordCol").hidden = !showingPasswords; |
michael@0 | 145 | _filterPasswords(); |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | // Notify observers that the password visibility toggling is |
michael@0 | 149 | // completed. (Mostly useful for tests) |
michael@0 | 150 | Components.classes["@mozilla.org/observer-service;1"] |
michael@0 | 151 | .getService(Components.interfaces.nsIObserverService) |
michael@0 | 152 | .notifyObservers(null, "passwordmgr-password-toggle-complete", null); |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | function AskUserShowPasswords() { |
michael@0 | 156 | var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService); |
michael@0 | 157 | var dummy = { value: false }; |
michael@0 | 158 | |
michael@0 | 159 | // Confirm the user wants to display passwords |
michael@0 | 160 | return prompter.confirmEx(window, |
michael@0 | 161 | null, |
michael@0 | 162 | kSignonBundle.getString("noMasterPasswordPrompt"), prompter.STD_YES_NO_BUTTONS, |
michael@0 | 163 | null, null, null, null, dummy) == 0; // 0=="Yes" button |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | function FinalizeSignonDeletions(syncNeeded) { |
michael@0 | 167 | for (var s=0; s<deletedSignons.length; s++) { |
michael@0 | 168 | passwordmanager.removeLogin(deletedSignons[s]); |
michael@0 | 169 | } |
michael@0 | 170 | // If the deletion has been performed in a filtered view, reflect the deletion in the unfiltered table. |
michael@0 | 171 | // See bug 405389. |
michael@0 | 172 | if (syncNeeded) { |
michael@0 | 173 | try { |
michael@0 | 174 | signons = passwordmanager.getAllLogins(); |
michael@0 | 175 | } catch (e) { |
michael@0 | 176 | signons = []; |
michael@0 | 177 | } |
michael@0 | 178 | } |
michael@0 | 179 | deletedSignons.length = 0; |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | function HandleSignonKeyPress(e) { |
michael@0 | 183 | if (e.keyCode == 46) { |
michael@0 | 184 | DeleteSignon(); |
michael@0 | 185 | } |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | function getColumnByName(column) { |
michael@0 | 189 | switch (column) { |
michael@0 | 190 | case "hostname": |
michael@0 | 191 | return document.getElementById("siteCol"); |
michael@0 | 192 | case "username": |
michael@0 | 193 | return document.getElementById("userCol"); |
michael@0 | 194 | case "password": |
michael@0 | 195 | return document.getElementById("passwordCol"); |
michael@0 | 196 | } |
michael@0 | 197 | } |
michael@0 | 198 | |
michael@0 | 199 | var lastSignonSortColumn = "hostname"; |
michael@0 | 200 | var lastSignonSortAscending = true; |
michael@0 | 201 | |
michael@0 | 202 | function SignonColumnSort(column) { |
michael@0 | 203 | // clear out the sortDirection attribute on the old column |
michael@0 | 204 | var lastSortedCol = getColumnByName(lastSignonSortColumn); |
michael@0 | 205 | lastSortedCol.removeAttribute("sortDirection"); |
michael@0 | 206 | |
michael@0 | 207 | // sort |
michael@0 | 208 | lastSignonSortAscending = |
michael@0 | 209 | SortTree(signonsTree, signonsTreeView, |
michael@0 | 210 | signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons, |
michael@0 | 211 | column, lastSignonSortColumn, lastSignonSortAscending); |
michael@0 | 212 | lastSignonSortColumn = column; |
michael@0 | 213 | |
michael@0 | 214 | // set the sortDirection attribute to get the styling going |
michael@0 | 215 | // first we need to get the right element |
michael@0 | 216 | var sortedCol = getColumnByName(column); |
michael@0 | 217 | sortedCol.setAttribute("sortDirection", lastSignonSortAscending ? |
michael@0 | 218 | "ascending" : "descending"); |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | function SignonClearFilter() { |
michael@0 | 222 | var singleSelection = (signonsTreeView.selection.count == 1); |
michael@0 | 223 | |
michael@0 | 224 | // Clear the Tree Display |
michael@0 | 225 | signonsTreeView.rowCount = 0; |
michael@0 | 226 | signonsTree.treeBoxObject.rowCountChanged(0, -signonsTreeView._filterSet.length); |
michael@0 | 227 | signonsTreeView._filterSet = []; |
michael@0 | 228 | |
michael@0 | 229 | // Just reload the list to make sure deletions are respected |
michael@0 | 230 | LoadSignons(); |
michael@0 | 231 | |
michael@0 | 232 | // Restore selection |
michael@0 | 233 | if (singleSelection) { |
michael@0 | 234 | signonsTreeView.selection.clearSelection(); |
michael@0 | 235 | for (let i = 0; i < signonsTreeView._lastSelectedRanges.length; ++i) { |
michael@0 | 236 | var range = signonsTreeView._lastSelectedRanges[i]; |
michael@0 | 237 | signonsTreeView.selection.rangedSelect(range.min, range.max, true); |
michael@0 | 238 | } |
michael@0 | 239 | } else { |
michael@0 | 240 | signonsTreeView.selection.select(0); |
michael@0 | 241 | } |
michael@0 | 242 | signonsTreeView._lastSelectedRanges = []; |
michael@0 | 243 | |
michael@0 | 244 | document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsSpielAll"); |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | function FocusFilterBox() { |
michael@0 | 248 | var filterBox = document.getElementById("filter"); |
michael@0 | 249 | if (filterBox.getAttribute("focused") != "true") |
michael@0 | 250 | filterBox.focus(); |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | function SignonMatchesFilter(aSignon, aFilterValue) { |
michael@0 | 254 | if (aSignon.hostname.toLowerCase().indexOf(aFilterValue) != -1) |
michael@0 | 255 | return true; |
michael@0 | 256 | if (aSignon.username && |
michael@0 | 257 | aSignon.username.toLowerCase().indexOf(aFilterValue) != -1) |
michael@0 | 258 | return true; |
michael@0 | 259 | if (aSignon.httpRealm && |
michael@0 | 260 | aSignon.httpRealm.toLowerCase().indexOf(aFilterValue) != -1) |
michael@0 | 261 | return true; |
michael@0 | 262 | if (showingPasswords && aSignon.password && |
michael@0 | 263 | aSignon.password.toLowerCase().indexOf(aFilterValue) != -1) |
michael@0 | 264 | return true; |
michael@0 | 265 | |
michael@0 | 266 | return false; |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | function FilterPasswords(aFilterValue, view) { |
michael@0 | 270 | aFilterValue = aFilterValue.toLowerCase(); |
michael@0 | 271 | return signons.filter(function (s) SignonMatchesFilter(s, aFilterValue)); |
michael@0 | 272 | } |
michael@0 | 273 | |
michael@0 | 274 | function SignonSaveState() { |
michael@0 | 275 | // Save selection |
michael@0 | 276 | var seln = signonsTreeView.selection; |
michael@0 | 277 | signonsTreeView._lastSelectedRanges = []; |
michael@0 | 278 | var rangeCount = seln.getRangeCount(); |
michael@0 | 279 | for (var i = 0; i < rangeCount; ++i) { |
michael@0 | 280 | var min = {}; var max = {}; |
michael@0 | 281 | seln.getRangeAt(i, min, max); |
michael@0 | 282 | signonsTreeView._lastSelectedRanges.push({ min: min.value, max: max.value }); |
michael@0 | 283 | } |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | function _filterPasswords() |
michael@0 | 287 | { |
michael@0 | 288 | var filter = document.getElementById("filter").value; |
michael@0 | 289 | if (filter == "") { |
michael@0 | 290 | SignonClearFilter(); |
michael@0 | 291 | return; |
michael@0 | 292 | } |
michael@0 | 293 | |
michael@0 | 294 | var newFilterSet = FilterPasswords(filter, signonsTreeView); |
michael@0 | 295 | if (!signonsTreeView._filterSet.length) { |
michael@0 | 296 | // Save Display Info for the Non-Filtered mode when we first |
michael@0 | 297 | // enter Filtered mode. |
michael@0 | 298 | SignonSaveState(); |
michael@0 | 299 | } |
michael@0 | 300 | signonsTreeView._filterSet = newFilterSet; |
michael@0 | 301 | |
michael@0 | 302 | // Clear the display |
michael@0 | 303 | let oldRowCount = signonsTreeView.rowCount; |
michael@0 | 304 | signonsTreeView.rowCount = 0; |
michael@0 | 305 | signonsTree.treeBoxObject.rowCountChanged(0, -oldRowCount); |
michael@0 | 306 | // Set up the filtered display |
michael@0 | 307 | signonsTreeView.rowCount = signonsTreeView._filterSet.length; |
michael@0 | 308 | signonsTree.treeBoxObject.rowCountChanged(0, signonsTreeView.rowCount); |
michael@0 | 309 | |
michael@0 | 310 | // if the view is not empty then select the first item |
michael@0 | 311 | if (signonsTreeView.rowCount > 0) |
michael@0 | 312 | signonsTreeView.selection.select(0); |
michael@0 | 313 | |
michael@0 | 314 | document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsSpielFiltered"); |
michael@0 | 315 | } |
michael@0 | 316 | |
michael@0 | 317 | function CopyPassword() { |
michael@0 | 318 | // Don't copy passwords if we aren't already showing the passwords & a master |
michael@0 | 319 | // password hasn't been entered. |
michael@0 | 320 | if (!showingPasswords && !masterPasswordLogin()) |
michael@0 | 321 | return; |
michael@0 | 322 | // Copy selected signon's password to clipboard |
michael@0 | 323 | var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]. |
michael@0 | 324 | getService(Components.interfaces.nsIClipboardHelper); |
michael@0 | 325 | var row = document.getElementById("signonsTree").currentIndex; |
michael@0 | 326 | var password = signonsTreeView.getCellText(row, {id : "passwordCol" }); |
michael@0 | 327 | clipboard.copyString(password, document); |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | function CopyUsername() { |
michael@0 | 331 | // Copy selected signon's username to clipboard |
michael@0 | 332 | var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]. |
michael@0 | 333 | getService(Components.interfaces.nsIClipboardHelper); |
michael@0 | 334 | var row = document.getElementById("signonsTree").currentIndex; |
michael@0 | 335 | var username = signonsTreeView.getCellText(row, {id : "userCol" }); |
michael@0 | 336 | clipboard.copyString(username); |
michael@0 | 337 | } |
michael@0 | 338 | |
michael@0 | 339 | function UpdateCopyPassword() { |
michael@0 | 340 | var singleSelection = (signonsTreeView.selection.count == 1); |
michael@0 | 341 | var passwordMenuitem = document.getElementById("context-copypassword"); |
michael@0 | 342 | var usernameMenuitem = document.getElementById("context-copyusername"); |
michael@0 | 343 | if (singleSelection) { |
michael@0 | 344 | usernameMenuitem.removeAttribute("disabled"); |
michael@0 | 345 | passwordMenuitem.removeAttribute("disabled"); |
michael@0 | 346 | } else { |
michael@0 | 347 | usernameMenuitem.setAttribute("disabled", "true"); |
michael@0 | 348 | passwordMenuitem.setAttribute("disabled", "true"); |
michael@0 | 349 | } |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | function masterPasswordLogin(noPasswordCallback) { |
michael@0 | 353 | // This doesn't harm if passwords are not encrypted |
michael@0 | 354 | var tokendb = Components.classes["@mozilla.org/security/pk11tokendb;1"] |
michael@0 | 355 | .createInstance(Components.interfaces.nsIPK11TokenDB); |
michael@0 | 356 | var token = tokendb.getInternalKeyToken(); |
michael@0 | 357 | |
michael@0 | 358 | // If there is no master password, still give the user a chance to opt-out of displaying passwords |
michael@0 | 359 | if (token.checkPassword("")) |
michael@0 | 360 | return noPasswordCallback ? noPasswordCallback() : true; |
michael@0 | 361 | |
michael@0 | 362 | // So there's a master password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl). |
michael@0 | 363 | try { |
michael@0 | 364 | // Relogin and ask for the master password. |
michael@0 | 365 | token.login(true); // 'true' means always prompt for token password. User will be prompted until |
michael@0 | 366 | // clicking 'Cancel' or entering the correct password. |
michael@0 | 367 | } catch (e) { |
michael@0 | 368 | // An exception will be thrown if the user cancels the login prompt dialog. |
michael@0 | 369 | // User is also logged out of Software Security Device. |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | return token.isLoggedIn(); |
michael@0 | 373 | } |