Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
const { XPCOMUtils } = ChromeUtils.import(
7
);
8
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
9
const { PrivateBrowsingUtils } = ChromeUtils.import(
11
);
12
13
/* eslint-disable block-scoped-var, no-var */
14
15
ChromeUtils.defineModuleGetter(
16
this,
17
"LoginHelper",
19
);
20
21
XPCOMUtils.defineLazyGetter(this, "strBundle", () => {
22
return Services.strings.createBundle(
24
);
25
});
26
27
const LoginInfo = Components.Constructor(
28
"@mozilla.org/login-manager/loginInfo;1",
29
"nsILoginInfo",
30
"init"
31
);
32
33
const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
34
35
/**
36
* The maximum age of the password in ms (using `timePasswordChanged`) whereby
37
* a user can toggle the password visibility in a doorhanger to add a username to
38
* a saved login.
39
*/
40
const VISIBILITY_TOGGLE_MAX_PW_AGE_MS = 2 * 60 * 1000; // 2 minutes
41
42
/**
43
* Constants for password prompt telemetry.
44
*/
45
const PROMPT_DISPLAYED = 0;
46
47
const PROMPT_ADD_OR_UPDATE = 1;
48
const PROMPT_NOTNOW = 2;
49
const PROMPT_NEVER = 3;
50
51
/**
52
* The minimum age of a doorhanger in ms before it will get removed after a locationchange
53
*/
54
const NOTIFICATION_TIMEOUT_MS = 10 * 1000; // 10 seconds
55
56
/**
57
* The minimum age of an attention-requiring dismissed doorhanger in ms
58
* before it will get removed after a locationchange
59
*/
60
const ATTENTION_NOTIFICATION_TIMEOUT_MS = 60 * 1000; // 1 minute
61
62
/**
63
* Implements interfaces for prompting the user to enter/save/change login info
64
* found in HTML forms.
65
*/
66
class LoginManagerPrompter {
67
get classID() {
68
return Components.ID("{c47ff942-9678-44a5-bc9b-05e0d676c79c}");
69
}
70
71
get QueryInterface() {
72
return ChromeUtils.generateQI([Ci.nsILoginManagerPrompter]);
73
}
74
75
promptToSavePassword(
76
aBrowser,
77
aLogin,
78
dismissed = false,
79
notifySaved = false,
80
autoFilledLoginGuid = ""
81
) {
82
log.debug("promptToSavePassword");
83
let inPrivateBrowsing = PrivateBrowsingUtils.isBrowserPrivate(aBrowser);
84
LoginManagerPrompter._showLoginCaptureDoorhanger(
85
aBrowser,
86
aLogin,
87
"password-save",
88
{
89
dismissed: inPrivateBrowsing || dismissed,
90
extraAttr: notifySaved ? "attention" : "",
91
},
92
{
93
notifySaved,
94
autoFilledLoginGuid,
95
}
96
);
97
Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
98
}
99
100
/**
101
* Displays the PopupNotifications.jsm doorhanger for password save or change.
102
*
103
* @param {Element} browser
104
* The browser to show the doorhanger on.
105
* @param {nsILoginInfo} login
106
* Login to save or change. For changes, this login should contain the
107
* new password and/or username
108
* @param {string} type
109
* This is "password-save" or "password-change" depending on the
110
* original notification type. This is used for telemetry and tests.
111
* @param {object} showOptions
112
* Options to pass along to PopupNotifications.show().
113
* @param {bool} [options.notifySaved = false]
114
* Whether to indicate to the user that the login was already saved.
115
* @param {string} [options.messageStringID = undefined]
116
* An optional string ID to override the default message.
117
* @param {string} [options.autoSavedLoginGuid = ""]
118
* A string guid value for the auto-saved login to be removed if the changes
119
* match it to a different login
120
* @param {string} [options.autoFilledLoginGuid = ""]
121
* A string guid value for the autofilled login
122
*/
123
static _showLoginCaptureDoorhanger(
124
browser,
125
login,
126
type,
127
showOptions = {},
128
{
129
notifySaved = false,
130
messageStringID,
131
autoSavedLoginGuid = "",
132
autoFilledLoginGuid = "",
133
} = {}
134
) {
135
log.debug(
136
`_showLoginCaptureDoorhanger, got autoSavedLoginGuid: ${autoSavedLoginGuid}`
137
);
138
log.debug(
139
`_showLoginCaptureDoorhanger, got autoFilledLoginGuid: ${autoFilledLoginGuid}`
140
);
141
142
let saveMsgNames = {
143
prompt: login.username === "" ? "saveLoginMsgNoUser" : "saveLoginMsg",
144
buttonLabel: "saveLoginButtonAllow.label",
145
buttonAccessKey: "saveLoginButtonAllow.accesskey",
146
secondaryButtonLabel: "saveLoginButtonDeny.label",
147
secondaryButtonAccessKey: "saveLoginButtonDeny.accesskey",
148
};
149
150
let changeMsgNames = {
151
prompt: login.username === "" ? "updateLoginMsgNoUser" : "updateLoginMsg",
152
buttonLabel: "updateLoginButtonText",
153
buttonAccessKey: "updateLoginButtonAccessKey",
154
secondaryButtonLabel: "updateLoginButtonDeny.label",
155
secondaryButtonAccessKey: "updateLoginButtonDeny.accesskey",
156
};
157
158
let initialMsgNames =
159
type == "password-save" ? saveMsgNames : changeMsgNames;
160
161
if (messageStringID) {
162
changeMsgNames.prompt = messageStringID;
163
}
164
165
let brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
166
let brandShortName = brandBundle.GetStringFromName("brandShortName");
167
let host = this._getShortDisplayHost(login.origin);
168
let promptMsg =
169
type == "password-save"
170
? this._getLocalizedString(saveMsgNames.prompt, [brandShortName, host])
171
: this._getLocalizedString(changeMsgNames.prompt);
172
173
let histogramName =
174
type == "password-save"
175
? "PWMGR_PROMPT_REMEMBER_ACTION"
176
: "PWMGR_PROMPT_UPDATE_ACTION";
177
let histogram = Services.telemetry.getHistogramById(histogramName);
178
179
let chromeDoc = browser.ownerDocument;
180
let currentNotification;
181
182
let updateButtonStatus = element => {
183
let mainActionButton = element.button;
184
// Disable the main button inside the menu-button if the password field is empty.
185
if (!login.password.length) {
186
mainActionButton.setAttribute("disabled", true);
187
chromeDoc
188
.getElementById("password-notification-password")
189
.classList.add("popup-notification-invalid-input");
190
} else {
191
mainActionButton.removeAttribute("disabled");
192
chromeDoc
193
.getElementById("password-notification-password")
194
.classList.remove("popup-notification-invalid-input");
195
}
196
};
197
198
let updateButtonLabel = () => {
199
if (!currentNotification) {
200
Cu.reportError("updateButtonLabel, no currentNotification");
201
}
202
let foundLogins = LoginHelper.searchLoginsWithObject({
203
formActionOrigin: login.formActionOrigin,
204
origin: login.origin,
205
httpRealm: login.httpRealm,
206
schemeUpgrades: LoginHelper.schemeUpgrades,
207
});
208
209
let logins = this._filterUpdatableLogins(
210
login,
211
foundLogins,
212
autoSavedLoginGuid
213
);
214
let msgNames = !logins.length ? saveMsgNames : changeMsgNames;
215
216
// Update the label based on whether this will be a new login or not.
217
let label = this._getLocalizedString(msgNames.buttonLabel);
218
let accessKey = this._getLocalizedString(msgNames.buttonAccessKey);
219
220
// Update the labels for the next time the panel is opened.
221
currentNotification.mainAction.label = label;
222
currentNotification.mainAction.accessKey = accessKey;
223
224
// Update the labels in real time if the notification is displayed.
225
let element = [...currentNotification.owner.panel.childNodes].find(
226
n => n.notification == currentNotification
227
);
228
if (element) {
229
element.setAttribute("buttonlabel", label);
230
element.setAttribute("buttonaccesskey", accessKey);
231
updateButtonStatus(element);
232
}
233
};
234
235
let writeDataToUI = () => {
236
let nameField = chromeDoc.getElementById(
237
"password-notification-username"
238
);
239
nameField.placeholder = usernamePlaceholder;
240
nameField.value = login.username;
241
242
let toggleCheckbox = chromeDoc.getElementById(
243
"password-notification-visibilityToggle"
244
);
245
toggleCheckbox.removeAttribute("checked");
246
let passwordField = chromeDoc.getElementById(
247
"password-notification-password"
248
);
249
// Ensure the type is reset so the field is masked.
250
passwordField.type = "password";
251
passwordField.value = login.password;
252
updateButtonLabel();
253
};
254
255
let readDataFromUI = () => {
256
login.username = chromeDoc.getElementById(
257
"password-notification-username"
258
).value;
259
login.password = chromeDoc.getElementById(
260
"password-notification-password"
261
).value;
262
};
263
264
let onInput = () => {
265
readDataFromUI();
266
updateButtonLabel();
267
};
268
269
let onKeyUp = e => {
270
if (e.key == "Enter") {
271
e.target.closest("popupnotification").button.doCommand();
272
}
273
};
274
275
let onVisibilityToggle = commandEvent => {
276
let passwordField = chromeDoc.getElementById(
277
"password-notification-password"
278
);
279
// Gets the caret position before changing the type of the textbox
280
let selectionStart = passwordField.selectionStart;
281
let selectionEnd = passwordField.selectionEnd;
282
passwordField.setAttribute(
283
"type",
284
commandEvent.target.checked ? "" : "password"
285
);
286
if (!passwordField.hasAttribute("focused")) {
287
return;
288
}
289
passwordField.selectionStart = selectionStart;
290
passwordField.selectionEnd = selectionEnd;
291
};
292
293
let persistData = () => {
294
let foundLogins = LoginHelper.searchLoginsWithObject({
295
formActionOrigin: login.formActionOrigin,
296
origin: login.origin,
297
httpRealm: login.httpRealm,
298
schemeUpgrades: LoginHelper.schemeUpgrades,
299
});
300
301
let logins = this._filterUpdatableLogins(
302
login,
303
foundLogins,
304
autoSavedLoginGuid
305
);
306
let resolveBy = ["scheme", "timePasswordChanged"];
307
logins = LoginHelper.dedupeLogins(
308
logins,
309
["username"],
310
resolveBy,
311
login.origin
312
);
313
// sort exact username matches to the top
314
logins.sort(l => (l.username == login.username ? -1 : 1));
315
316
log.debug(`persistData: Matched ${logins.length} logins`);
317
318
let loginToRemove;
319
let loginToUpdate = logins.shift();
320
321
if (logins.length && logins[0].guid == autoSavedLoginGuid) {
322
loginToRemove = logins.shift();
323
}
324
if (logins.length) {
325
log.warn(
326
logins.length,
327
"other updatable logins!",
328
logins.map(l => l.guid),
329
"loginToUpdate:",
330
loginToUpdate && loginToUpdate.guid,
331
"loginToRemove:",
332
loginToRemove && loginToRemove.guid
333
);
334
// Proceed with updating the login with the best username match rather
335
// than returning and losing the edit.
336
}
337
338
if (!loginToUpdate) {
339
// Create a new login, don't update an original.
340
// The original login we have been provided with might have its own
341
// metadata, but we don't want it propagated to the newly created one.
342
Services.logins.addLogin(
343
new LoginInfo(
344
login.origin,
345
login.formActionOrigin,
346
login.httpRealm,
347
login.username,
348
login.password,
349
login.usernameField,
350
login.passwordField
351
)
352
);
353
} else if (
354
loginToUpdate.password == login.password &&
355
loginToUpdate.username == login.username
356
) {
357
// We only want to touch the login's use count and last used time.
358
log.debug("persistData: Touch matched login", loginToUpdate.guid);
359
Services.logins.recordPasswordUse(loginToUpdate);
360
} else {
361
log.debug("persistData: Update matched login", loginToUpdate.guid);
362
this._updateLogin(loginToUpdate, login);
363
// notify that this auto-saved login has been merged
364
if (loginToRemove && loginToRemove.guid == autoSavedLoginGuid) {
365
Services.obs.notifyObservers(
366
loginToRemove,
367
"passwordmgr-autosaved-login-merged"
368
);
369
}
370
}
371
372
if (loginToRemove) {
373
log.debug("persistData: removing login", loginToRemove.guid);
374
Services.logins.removeLogin(loginToRemove);
375
}
376
};
377
378
// The main action is the "Save" or "Update" button.
379
let mainAction = {
380
label: this._getLocalizedString(initialMsgNames.buttonLabel),
381
accessKey: this._getLocalizedString(initialMsgNames.buttonAccessKey),
382
callback: () => {
383
histogram.add(PROMPT_ADD_OR_UPDATE);
384
if (histogramName == "PWMGR_PROMPT_REMEMBER_ACTION") {
385
Services.obs.notifyObservers(browser, "LoginStats:NewSavedPassword");
386
}
387
readDataFromUI();
388
persistData();
389
Services.obs.notifyObservers(
390
null,
391
"weave:telemetry:histogram",
392
histogramName
393
);
394
browser.focus();
395
},
396
};
397
398
let secondaryActions = [
399
{
400
label: this._getLocalizedString(initialMsgNames.secondaryButtonLabel),
401
accessKey: this._getLocalizedString(
402
initialMsgNames.secondaryButtonAccessKey
403
),
404
callback: () => {
405
histogram.add(PROMPT_NOTNOW);
406
Services.obs.notifyObservers(
407
null,
408
"weave:telemetry:histogram",
409
histogramName
410
);
411
browser.focus();
412
},
413
},
414
];
415
// Include a "Never for this site" button when saving a new password.
416
if (type == "password-save") {
417
secondaryActions.push({
418
label: this._getLocalizedString("saveLoginButtonNever.label"),
419
accessKey: this._getLocalizedString("saveLoginButtonNever.accesskey"),
420
callback: () => {
421
histogram.add(PROMPT_NEVER);
422
Services.obs.notifyObservers(
423
null,
424
"weave:telemetry:histogram",
425
histogramName
426
);
427
Services.logins.setLoginSavingEnabled(login.origin, false);
428
browser.focus();
429
},
430
});
431
}
432
433
let usernamePlaceholder = this._getLocalizedString("noUsernamePlaceholder");
434
let togglePasswordLabel = this._getLocalizedString("togglePasswordLabel");
435
let togglePasswordAccessKey = this._getLocalizedString(
436
"togglePasswordAccessKey2"
437
);
438
439
// .wrappedJSObject needed here -- see bug 422974 comment 5.
440
let { PopupNotifications } = browser.ownerGlobal.wrappedJSObject;
441
442
let notificationID = "password";
443
// keep attention notifications around for longer after a locationchange
444
const timeoutMs =
445
showOptions.dismissed && showOptions.extraAttr == "attention"
446
? ATTENTION_NOTIFICATION_TIMEOUT_MS
447
: NOTIFICATION_TIMEOUT_MS;
448
let notification = PopupNotifications.show(
449
browser,
450
notificationID,
451
promptMsg,
452
"password-notification-icon",
453
mainAction,
454
secondaryActions,
455
Object.assign(
456
{
457
timeout: Date.now() + timeoutMs,
458
persistWhileVisible: true,
459
passwordNotificationType: type,
460
hideClose: true,
461
eventCallback(topic) {
462
switch (topic) {
463
case "showing":
464
currentNotification = this;
465
466
// Record the first time this instance of the doorhanger is shown.
467
if (!this.timeShown) {
468
histogram.add(PROMPT_DISPLAYED);
469
Services.obs.notifyObservers(
470
null,
471
"weave:telemetry:histogram",
472
histogramName
473
);
474
}
475
476
chromeDoc
477
.getElementById("password-notification-password")
478
.removeAttribute("focused");
479
chromeDoc
480
.getElementById("password-notification-username")
481
.removeAttribute("focused");
482
chromeDoc
483
.getElementById("password-notification-username")
484
.addEventListener("input", onInput);
485
chromeDoc
486
.getElementById("password-notification-username")
487
.addEventListener("keyup", onKeyUp);
488
chromeDoc
489
.getElementById("password-notification-password")
490
.addEventListener("keyup", onKeyUp);
491
chromeDoc
492
.getElementById("password-notification-password")
493
.addEventListener("input", onInput);
494
let toggleBtn = chromeDoc.getElementById(
495
"password-notification-visibilityToggle"
496
);
497
498
if (
499
Services.prefs.getBoolPref(
500
"signon.rememberSignons.visibilityToggle"
501
)
502
) {
503
toggleBtn.addEventListener("command", onVisibilityToggle);
504
toggleBtn.setAttribute("label", togglePasswordLabel);
505
toggleBtn.setAttribute("accesskey", togglePasswordAccessKey);
506
507
let hideToggle =
508
LoginHelper.isMasterPasswordSet() ||
509
// Don't show the toggle when the login was autofilled
510
!!autoFilledLoginGuid ||
511
// Dismissed-by-default prompts should still show the toggle.
512
(this.timeShown && this.wasDismissed) ||
513
// If we are only adding a username then the password is
514
// one that is already saved and we don't want to reveal
515
// it as the submitter of this form may not be the account
516
// owner, they may just be using the saved password.
517
(messageStringID == "updateLoginMsgAddUsername" &&
518
login.timePasswordChanged <
519
Date.now() - VISIBILITY_TOGGLE_MAX_PW_AGE_MS);
520
toggleBtn.setAttribute("hidden", hideToggle);
521
}
522
break;
523
case "shown": {
524
writeDataToUI();
525
let anchorIcon = this.anchorElement;
526
if (anchorIcon && this.options.extraAttr == "attention") {
527
anchorIcon.removeAttribute("extraAttr");
528
delete this.options.extraAttr;
529
}
530
break;
531
}
532
case "dismissed":
533
this.wasDismissed = true;
534
readDataFromUI();
535
// Fall through.
536
case "removed":
537
currentNotification = null;
538
chromeDoc
539
.getElementById("password-notification-username")
540
.removeEventListener("input", onInput);
541
chromeDoc
542
.getElementById("password-notification-username")
543
.removeEventListener("keyup", onKeyUp);
544
chromeDoc
545
.getElementById("password-notification-password")
546
.removeEventListener("input", onInput);
547
chromeDoc
548
.getElementById("password-notification-password")
549
.removeEventListener("keyup", onKeyUp);
550
chromeDoc
551
.getElementById("password-notification-visibilityToggle")
552
.removeEventListener("command", onVisibilityToggle);
553
break;
554
}
555
return false;
556
},
557
},
558
showOptions
559
)
560
);
561
562
if (notifySaved) {
563
let anchor = notification.anchorElement;
564
log.debug("Showing the ConfirmationHint");
565
anchor.ownerGlobal.ConfirmationHint.show(anchor, "passwordSaved");
566
}
567
}
568
569
/**
570
* Called when we think we detect a password or username change for
571
* an existing login, when the form being submitted contains multiple
572
* password fields.
573
*
574
* @param {Element} aBrowser
575
* The browser element that the request came from.
576
* @param {nsILoginInfo} aOldLogin
577
* The old login we may want to update.
578
* @param {nsILoginInfo} aNewLogin
579
* The new login from the page form.
580
* @param {boolean} [dismissed = false]
581
* If the prompt should be automatically dismissed on being shown.
582
* @param {boolean} [notifySaved = false]
583
* Whether the notification should indicate that a login has been saved
584
* @param {string} [autoSavedLoginGuid = ""]
585
* A guid value for the old login to be removed if the changes match it
586
* to a different login
587
*/
588
promptToChangePassword(
589
aBrowser,
590
aOldLogin,
591
aNewLogin,
592
dismissed = false,
593
notifySaved = false,
594
autoSavedLoginGuid = "",
595
autoFilledLoginGuid = ""
596
) {
597
let login = aOldLogin.clone();
598
login.origin = aNewLogin.origin;
599
login.formActionOrigin = aNewLogin.formActionOrigin;
600
login.password = aNewLogin.password;
601
login.username = aNewLogin.username;
602
603
let messageStringID;
604
if (
605
aOldLogin.username === "" &&
606
login.username !== "" &&
607
login.password == aOldLogin.password
608
) {
609
// If the saved password matches the password we're prompting with then we
610
// are only prompting to let the user add a username since there was one in
611
// the form. Change the message so the purpose of the prompt is clearer.
612
messageStringID = "updateLoginMsgAddUsername";
613
}
614
615
LoginManagerPrompter._showLoginCaptureDoorhanger(
616
aBrowser,
617
login,
618
"password-change",
619
{
620
dismissed,
621
extraAttr: notifySaved ? "attention" : "",
622
},
623
{
624
notifySaved,
625
messageStringID,
626
autoSavedLoginGuid,
627
autoFilledLoginGuid,
628
}
629
);
630
631
let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
632
Services.obs.notifyObservers(
633
aNewLogin,
634
"passwordmgr-prompt-change",
635
oldGUID
636
);
637
}
638
639
/**
640
* Called when we detect a password change in a form submission, but we
641
* don't know which existing login (username) it's for. Asks the user
642
* to select a username and confirm the password change.
643
*
644
* Note: The caller doesn't know the username for aNewLogin, so this
645
* function fills in .username and .usernameField with the values
646
* from the login selected by the user.
647
*/
648
promptToChangePasswordWithUsernames(browser, logins, aNewLogin) {
649
log.debug("promptToChangePasswordWithUsernames with count:", logins.length);
650
651
var usernames = logins.map(
652
l => l.username || LoginManagerPrompter._getLocalizedString("noUsername")
653
);
654
var dialogText = LoginManagerPrompter._getLocalizedString(
655
"userSelectText2"
656
);
657
var dialogTitle = LoginManagerPrompter._getLocalizedString(
658
"passwordChangeTitle"
659
);
660
var selectedIndex = { value: null };
661
662
// If user selects ok, outparam.value is set to the index
663
// of the selected username.
664
var ok = Services.prompt.select(
665
browser.ownerGlobal,
666
dialogTitle,
667
dialogText,
668
usernames,
669
selectedIndex
670
);
671
if (ok) {
672
// Now that we know which login to use, modify its password.
673
var selectedLogin = logins[selectedIndex.value];
674
log.debug("Updating password for user", selectedLogin.username);
675
var newLoginWithUsername = Cc[
676
"@mozilla.org/login-manager/loginInfo;1"
677
].createInstance(Ci.nsILoginInfo);
678
newLoginWithUsername.init(
679
aNewLogin.origin,
680
aNewLogin.formActionOrigin,
681
aNewLogin.httpRealm,
682
selectedLogin.username,
683
aNewLogin.password,
684
selectedLogin.usernameField,
685
aNewLogin.passwordField
686
);
687
LoginManagerPrompter._updateLogin(selectedLogin, newLoginWithUsername);
688
}
689
}
690
691
/* ---------- Internal Methods ---------- */
692
693
static _updateLogin(login, aNewLogin) {
694
var now = Date.now();
695
var propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
696
Ci.nsIWritablePropertyBag
697
);
698
propBag.setProperty("formActionOrigin", aNewLogin.formActionOrigin);
699
propBag.setProperty("origin", aNewLogin.origin);
700
propBag.setProperty("password", aNewLogin.password);
701
propBag.setProperty("username", aNewLogin.username);
702
// Explicitly set the password change time here (even though it would
703
// be changed automatically), to ensure that it's exactly the same
704
// value as timeLastUsed.
705
propBag.setProperty("timePasswordChanged", now);
706
propBag.setProperty("timeLastUsed", now);
707
propBag.setProperty("timesUsedIncrement", 1);
708
Services.logins.modifyLogin(login, propBag);
709
}
710
711
/**
712
* Can be called as:
713
* _getLocalizedString("key1");
714
* _getLocalizedString("key2", ["arg1"]);
715
* _getLocalizedString("key3", ["arg1", "arg2"]);
716
* (etc)
717
*
718
* Returns the localized string for the specified key,
719
* formatted if required.
720
*
721
*/
722
static _getLocalizedString(key, formatArgs) {
723
if (formatArgs) {
724
return strBundle.formatStringFromName(key, formatArgs);
725
}
726
return strBundle.GetStringFromName(key);
727
}
728
729
/**
730
* Converts a login's origin field to a short string for
731
* prompting purposes. Eg, "http://foo.com" --> "foo.com", or
732
* "ftp://www.site.co.uk" --> "site.co.uk".
733
*/
734
static _getShortDisplayHost(aURIString) {
735
var displayHost;
736
737
var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
738
Ci.nsIIDNService
739
);
740
try {
741
var uri = Services.io.newURI(aURIString);
742
var baseDomain = Services.eTLD.getBaseDomain(uri);
743
displayHost = idnService.convertToDisplayIDN(baseDomain, {});
744
} catch (e) {
745
log.warn("_getShortDisplayHost couldn't process", aURIString);
746
}
747
748
if (!displayHost) {
749
displayHost = aURIString;
750
}
751
752
return displayHost;
753
}
754
755
/**
756
* This function looks for existing logins that can be updated
757
* to match a submitted login, instead of creating a new one.
758
*
759
* Given a login and a loginList, it filters the login list
760
* to find every login with either:
761
* - the same username as aLogin
762
* - the same password as aLogin and an empty username
763
* so the user can add a username.
764
* - the same guid as the given login when it has an empty username
765
*
766
* @param {nsILoginInfo} aLogin
767
* login to use as filter.
768
* @param {nsILoginInfo[]} aLoginList
769
* Array of logins to filter.
770
* @param {String} includeGUID
771
* guid value for login that not be filtered out
772
* @returns {nsILoginInfo[]} the filtered array of logins.
773
*/
774
static _filterUpdatableLogins(aLogin, aLoginList, includeGUID) {
775
return aLoginList.filter(
776
l =>
777
l.username == aLogin.username ||
778
(l.password == aLogin.password && !l.username) ||
779
(includeGUID && includeGUID == l.guid)
780
);
781
}
782
}
783
784
XPCOMUtils.defineLazyGetter(this, "log", () => {
785
return LoginHelper.createLogger("LoginManagerPrompter");
786
});
787
788
const EXPORTED_SYMBOLS = ["LoginManagerPrompter"];