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