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
"use strict";
6
7
const EXPORTED_SYMBOLS = ["LoginManagerContextMenu"];
8
9
const { XPCOMUtils } = ChromeUtils.import(
11
);
12
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13
14
ChromeUtils.defineModuleGetter(
15
this,
16
"LoginHelper",
18
);
19
ChromeUtils.defineModuleGetter(
20
this,
21
"LoginManagerParent",
23
);
24
XPCOMUtils.defineLazyGetter(this, "log", () => {
25
return LoginHelper.createLogger("LoginManagerContextMenu");
26
});
27
28
/**
29
* Password manager object for the browser contextual menu.
30
*/
31
this.LoginManagerContextMenu = {
32
/**
33
* Look for login items and add them to the contextual menu.
34
*
35
* @param {Object} inputElementIdentifier
36
* An identifier generated for the input element via ContentDOMReference.
37
* @param {xul:browser} browser
38
* The browser for the document the context menu was open on.
39
* @param {string} formOrigin
40
* The origin of the document that the context menu was activated from.
41
* This isn't the same as the browser's top-level document origin
42
* when subframes are involved.
43
* @returns {DocumentFragment} a document fragment with all the login items.
44
*/
45
addLoginsToMenu(inputElementIdentifier, browser, formOrigin) {
46
let foundLogins = this._findLogins(formOrigin);
47
48
if (!foundLogins.length) {
49
return null;
50
}
51
52
let fragment = browser.ownerDocument.createDocumentFragment();
53
let duplicateUsernames = this._findDuplicates(foundLogins);
54
for (let login of foundLogins) {
55
let item = fragment.ownerDocument.createXULElement("menuitem");
56
57
let username = login.username;
58
// If login is empty or duplicated we want to append a modification date to it.
59
if (!username || duplicateUsernames.has(username)) {
60
if (!username) {
61
username = this._getLocalizedString("noUsername");
62
}
63
let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
64
let time = this.dateAndTimeFormatter.format(
65
new Date(meta.timePasswordChanged)
66
);
67
username = this._getLocalizedString("loginHostAge", [username, time]);
68
}
69
item.setAttribute("label", username);
70
item.setAttribute("class", "context-login-item");
71
72
// login is bound so we can keep the reference to each object.
73
item.addEventListener(
74
"command",
75
function(login, event) {
76
this._fillTargetField(
77
login,
78
inputElementIdentifier,
79
browser,
80
formOrigin
81
);
82
}.bind(this, login)
83
);
84
85
fragment.appendChild(item);
86
}
87
88
return fragment;
89
},
90
91
/**
92
* Undoes the work of addLoginsToMenu for the same menu.
93
*
94
* @param {Document}
95
* The context menu owner document.
96
*/
97
clearLoginsFromMenu(document) {
98
let loginItems = document.getElementsByClassName("context-login-item");
99
while (loginItems.item(0)) {
100
loginItems.item(0).remove();
101
}
102
},
103
104
/**
105
* Show the password autocomplete UI with the generation option forced to appear.
106
*/
107
async useGeneratedPassword(inputElementIdentifier, documentURI, browser) {
108
let browsingContextId = inputElementIdentifier.browsingContextId;
109
let browsingContext = BrowsingContext.get(browsingContextId);
110
let actor = browsingContext.currentWindowGlobal.getActor("LoginManager");
111
112
actor.sendAsyncMessage("PasswordManager:useGeneratedPassword", {
113
inputElementIdentifier,
114
});
115
},
116
117
/**
118
* Find logins for the specified origin..
119
*
120
* @param {string} formOrigin
121
* Origin of the logins we want to find that has be sanitized by `getLoginOrigin`.
122
* This isn't the same as the browser's top-level document URI
123
* when subframes are involved.
124
*
125
* @returns {nsILoginInfo[]} a login list
126
*/
127
_findLogins(formOrigin) {
128
let searchParams = {
129
origin: formOrigin,
130
schemeUpgrades: LoginHelper.schemeUpgrades,
131
};
132
let logins = LoginHelper.searchLoginsWithObject(searchParams);
133
let resolveBy = ["scheme", "timePasswordChanged"];
134
logins = LoginHelper.dedupeLogins(
135
logins,
136
["username", "password"],
137
resolveBy,
138
formOrigin
139
);
140
141
// Sort logins in alphabetical order and by date.
142
logins.sort((loginA, loginB) => {
143
// Sort alphabetically
144
let result = loginA.username.localeCompare(loginB.username);
145
if (result) {
146
// Forces empty logins to be at the end
147
if (!loginA.username) {
148
return 1;
149
}
150
if (!loginB.username) {
151
return -1;
152
}
153
return result;
154
}
155
156
// Same username logins are sorted by last change date
157
let metaA = loginA.QueryInterface(Ci.nsILoginMetaInfo);
158
let metaB = loginB.QueryInterface(Ci.nsILoginMetaInfo);
159
return metaB.timePasswordChanged - metaA.timePasswordChanged;
160
});
161
162
return logins;
163
},
164
165
/**
166
* Find duplicate usernames in a login list.
167
*
168
* @param {nsILoginInfo[]} loginList
169
* A list of logins we want to look for duplicate usernames.
170
*
171
* @returns {Set} a set with the duplicate usernames.
172
*/
173
_findDuplicates(loginList) {
174
let seen = new Set();
175
let duplicates = new Set();
176
for (let login of loginList) {
177
if (seen.has(login.username)) {
178
duplicates.add(login.username);
179
}
180
seen.add(login.username);
181
}
182
return duplicates;
183
},
184
185
/**
186
* @param {nsILoginInfo} login
187
* The login we want to fill the form with.
188
* @param {Object} inputElementIdentifier
189
* An identifier generated for the input element via ContentDOMReference.
190
* @param {xul:browser} browser
191
* The target tab browser.
192
* @param {string} formOrigin
193
* Origin of the document we're filling after sanitization via
194
* `getLoginOrigin`.
195
* This isn't the same as the browser's top-level
196
* origin when subframes are involved.
197
*/
198
_fillTargetField(login, inputElementIdentifier, browser, formOrigin) {
199
let browsingContextId = inputElementIdentifier.browsingContextId;
200
let browsingContext = BrowsingContext.get(browsingContextId);
201
if (!browsingContext) {
202
return;
203
}
204
205
let actor = browsingContext.currentWindowGlobal.getActor("LoginManager");
206
if (!actor) {
207
return;
208
}
209
210
actor
211
.fillForm({
212
browser,
213
inputElementIdentifier,
214
loginFormOrigin: formOrigin,
215
login,
216
})
217
.catch(Cu.reportError);
218
},
219
220
/**
221
* @param {string} key
222
* The localized string key
223
* @param {string[]} formatArgs
224
* An array of formatting argument string
225
*
226
* @returns {string} the localized string for the specified key,
227
* formatted with arguments if required.
228
*/
229
_getLocalizedString(key, formatArgs) {
230
if (formatArgs) {
231
return this._stringBundle.formatStringFromName(key, formatArgs);
232
}
233
return this._stringBundle.GetStringFromName(key);
234
},
235
};
236
237
XPCOMUtils.defineLazyGetter(
238
LoginManagerContextMenu,
239
"_stringBundle",
240
function() {
241
return Services.strings.createBundle(
243
);
244
}
245
);
246
247
XPCOMUtils.defineLazyGetter(
248
LoginManagerContextMenu,
249
"dateAndTimeFormatter",
250
function() {
251
return new Services.intl.DateTimeFormat(undefined, {
252
dateStyle: "medium",
253
});
254
}
255
);