Commit cdedc6cb authored by Matthew Noorenberghe's avatar Matthew Noorenberghe
Browse files

Bug 1532805 - Move UserAutoCompleteResult to its own file and rename to...

Bug 1532805 - Move UserAutoCompleteResult to its own file and rename to LoginAutoCompleteResult. r=prathiksha

Differential Revision: https://phabricator.services.mozilla.com/D22419

--HG--
rename : toolkit/components/passwordmgr/LoginManagerContent.jsm => toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
rename : toolkit/components/passwordmgr/test/unit/test_user_autocomplete_result.js => toolkit/components/passwordmgr/test/unit/test_login_autocomplete_result.js
extra : moz-landing-system : lando
parent 1944262d
/* 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/. */
/**
* nsIAutoCompleteResult implementation for saved logins.
*/
"use strict";
var EXPORTED_SYMBOLS = ["LoginAutoCompleteResult"];
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("LoginAutoCompleteResult");
return logger.log.bind(logger);
});
// nsIAutoCompleteResult implementation
function LoginAutoCompleteResult(aSearchString, matchingLogins, {isSecure, messageManager, isPasswordField, hostname}) {
function loginSort(a, b) {
let userA = a.username.toLowerCase();
let userB = b.username.toLowerCase();
if (userA < userB) {
return -1;
}
if (userA > userB) {
return 1;
}
return 0;
}
function findDuplicates(loginList) {
let seen = new Set();
let duplicates = new Set();
for (let login of loginList) {
if (seen.has(login.username)) {
duplicates.add(login.username);
}
seen.add(login.username);
}
return duplicates;
}
this._showInsecureFieldWarning = (!isSecure && LoginHelper.showInsecureFieldWarning) ? 1 : 0;
this._showAutoCompleteFooter = LoginHelper.showAutoCompleteFooter ? 1 : 0;
this.searchString = aSearchString;
this.logins = matchingLogins.sort(loginSort);
this.matchCount = matchingLogins.length + this._showInsecureFieldWarning + this._showAutoCompleteFooter;
this._messageManager = messageManager;
this._stringBundle = Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
this._dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined, { dateStyle: "medium" });
this._isPasswordField = isPasswordField;
this._hostname = hostname;
this._duplicateUsernames = findDuplicates(matchingLogins);
if (this.matchCount > 0) {
this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
this.defaultIndex = 0;
}
}
LoginAutoCompleteResult.prototype = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIAutoCompleteResult,
Ci.nsISupportsWeakReference]),
// private
logins: null,
// Allow autoCompleteSearch to get at the JS object so it can
// modify some readonly properties for internal use.
get wrappedJSObject() {
return this;
},
// Interfaces from idl...
searchString: null,
searchResult: Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
defaultIndex: -1,
errorDescription: "",
matchCount: 0,
getValueAt(index) {
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
if (this._showInsecureFieldWarning && index === 0) {
return "";
}
if (this._showAutoCompleteFooter && index === this.matchCount - 1) {
return "";
}
let selectedLogin = this.logins[index - this._showInsecureFieldWarning];
return this._isPasswordField ? selectedLogin.password : selectedLogin.username;
},
getLabelAt(index) {
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
let getLocalizedString = (key, formatArgs = null) => {
if (formatArgs) {
return this._stringBundle.formatStringFromName(key, formatArgs, formatArgs.length);
}
return this._stringBundle.GetStringFromName(key);
};
if (this._showInsecureFieldWarning && index === 0) {
let learnMoreString = getLocalizedString("insecureFieldWarningLearnMore");
return getLocalizedString("insecureFieldWarningDescription2", [learnMoreString]);
} else if (this._showAutoCompleteFooter && index === this.matchCount - 1) {
return JSON.stringify({
label: getLocalizedString("viewSavedLogins.label"),
hostname: this._hostname,
});
}
let login = this.logins[index - this._showInsecureFieldWarning];
let username = login.username;
// If login is empty or duplicated we want to append a modification date to it.
if (!username || this._duplicateUsernames.has(username)) {
if (!username) {
username = getLocalizedString("noUsername");
}
let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
let time = this._dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
username = getLocalizedString("loginHostAge", [username, time]);
}
return username;
},
getCommentAt(index) {
return "";
},
getStyleAt(index) {
if (index == 0 && this._showInsecureFieldWarning) {
return "insecureWarning";
} else if (this._showAutoCompleteFooter && index == this.matchCount - 1) {
return "loginsFooter";
}
return "login";
},
getImageAt(index) {
return "";
},
getFinalCompleteValueAt(index) {
return this.getValueAt(index);
},
removeValueAt(index, removeFromDB) {
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
if (this._showInsecureFieldWarning && index === 0) {
// Ignore the warning message item.
return;
}
if (this._showInsecureFieldWarning) {
index--;
}
// The user cannot delete the autocomplete footer.
if (this._showAutoCompleteFooter && index === this.matchCount - 1) {
return;
}
let [removedLogin] = this.logins.splice(index, 1);
this.matchCount--;
if (this.defaultIndex > this.logins.length) {
this.defaultIndex--;
}
if (removeFromDB) {
if (this._messageManager) {
let vanilla = LoginHelper.loginToVanillaObject(removedLogin);
this._messageManager.sendAsyncMessage("PasswordManager:removeLogin",
{ login: vanilla });
} else {
Services.logins.removeLogin(removedLogin);
}
}
},
};
......@@ -17,8 +17,8 @@ ChromeUtils.defineModuleGetter(this, "LoginFormFactory",
"resource://gre/modules/LoginManagerContent.jsm");
ChromeUtils.defineModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
ChromeUtils.defineModuleGetter(this, "UserAutoCompleteResult",
"resource://gre/modules/LoginManagerContent.jsm");
ChromeUtils.defineModuleGetter(this, "LoginAutoCompleteResult",
"resource://gre/modules/LoginAutoCompleteResult.jsm");
ChromeUtils.defineModuleGetter(this, "InsecurePasswordUtils",
"resource://gre/modules/InsecurePasswordUtils.jsm");
......@@ -534,7 +534,7 @@ LoginManager.prototype = {
}
this._autoCompleteLookupPromise = null;
let results = new UserAutoCompleteResult(aSearchString, logins, {
let results = new LoginAutoCompleteResult(aSearchString, logins, {
messageManager,
isSecure,
isPasswordField,
......
......@@ -11,9 +11,10 @@
"use strict";
var EXPORTED_SYMBOLS = [ "LoginManagerContent",
"LoginFormFactory",
"UserAutoCompleteResult" ];
var EXPORTED_SYMBOLS = [
"LoginManagerContent",
"LoginFormFactory",
];
const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
const AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS = 400;
......@@ -1564,189 +1565,6 @@ var LoginManagerContent = {
},
};
// nsIAutoCompleteResult implementation
function UserAutoCompleteResult(aSearchString, matchingLogins, {isSecure, messageManager, isPasswordField, hostname}) {
function loginSort(a, b) {
var userA = a.username.toLowerCase();
var userB = b.username.toLowerCase();
if (userA < userB) {
return -1;
}
if (userA > userB) {
return 1;
}
return 0;
}
function findDuplicates(loginList) {
let seen = new Set();
let duplicates = new Set();
for (let login of loginList) {
if (seen.has(login.username)) {
duplicates.add(login.username);
}
seen.add(login.username);
}
return duplicates;
}
this._showInsecureFieldWarning = (!isSecure && LoginHelper.showInsecureFieldWarning) ? 1 : 0;
this._showAutoCompleteFooter = LoginHelper.showAutoCompleteFooter ? 1 : 0;
this.searchString = aSearchString;
this.logins = matchingLogins.sort(loginSort);
this.matchCount = matchingLogins.length + this._showInsecureFieldWarning + this._showAutoCompleteFooter;
this._messageManager = messageManager;
this._stringBundle = Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
this._dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined, { dateStyle: "medium" });
this._isPasswordField = isPasswordField;
this._hostname = hostname;
this._duplicateUsernames = findDuplicates(matchingLogins);
if (this.matchCount > 0) {
this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
this.defaultIndex = 0;
}
}
UserAutoCompleteResult.prototype = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIAutoCompleteResult,
Ci.nsISupportsWeakReference]),
// private
logins: null,
// Allow autoCompleteSearch to get at the JS object so it can
// modify some readonly properties for internal use.
get wrappedJSObject() {
return this;
},
// Interfaces from idl...
searchString: null,
searchResult: Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
defaultIndex: -1,
errorDescription: "",
matchCount: 0,
getValueAt(index) {
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
if (this._showInsecureFieldWarning && index === 0) {
return "";
}
if (this._showAutoCompleteFooter && index === this.matchCount - 1) {
return "";
}
let selectedLogin = this.logins[index - this._showInsecureFieldWarning];
return this._isPasswordField ? selectedLogin.password : selectedLogin.username;
},
getLabelAt(index) {
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
let getLocalizedString = (key, formatArgs = null) => {
if (formatArgs) {
return this._stringBundle.formatStringFromName(key, formatArgs, formatArgs.length);
}
return this._stringBundle.GetStringFromName(key);
};
if (this._showInsecureFieldWarning && index === 0) {
let learnMoreString = getLocalizedString("insecureFieldWarningLearnMore");
return getLocalizedString("insecureFieldWarningDescription2", [learnMoreString]);
} else if (this._showAutoCompleteFooter && index === this.matchCount - 1) {
return JSON.stringify({
label: getLocalizedString("viewSavedLogins.label"),
hostname: this._hostname,
});
}
let login = this.logins[index - this._showInsecureFieldWarning];
let username = login.username;
// If login is empty or duplicated we want to append a modification date to it.
if (!username || this._duplicateUsernames.has(username)) {
if (!username) {
username = getLocalizedString("noUsername");
}
let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
let time = this._dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
username = getLocalizedString("loginHostAge", [username, time]);
}
return username;
},
getCommentAt(index) {
return "";
},
getStyleAt(index) {
if (index == 0 && this._showInsecureFieldWarning) {
return "insecureWarning";
} else if (this._showAutoCompleteFooter && index == this.matchCount - 1) {
return "loginsFooter";
}
return "login";
},
getImageAt(index) {
return "";
},
getFinalCompleteValueAt(index) {
return this.getValueAt(index);
},
removeValueAt(index, removeFromDB) {
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
if (this._showInsecureFieldWarning && index === 0) {
// Ignore the warning message item.
return;
}
if (this._showInsecureFieldWarning) {
index--;
}
// The user cannot delete the autocomplete footer.
if (this._showAutoCompleteFooter && index === this.matchCount - 1) {
return;
}
var [removedLogin] = this.logins.splice(index, 1);
this.matchCount--;
if (this.defaultIndex > this.logins.length) {
this.defaultIndex--;
}
if (removeFromDB) {
if (this._messageManager) {
let vanilla = LoginHelper.loginToVanillaObject(removedLogin);
this._messageManager.sendAsyncMessage("PasswordManager:removeLogin",
{ login: vanilla });
} else {
Services.logins.removeLogin(removedLogin);
}
}
},
};
/**
* A factory to generate FormLike objects that represent a set of login fields
......
......@@ -31,6 +31,7 @@ XPIDL_MODULE = 'loginmgr'
EXTRA_JS_MODULES += [
'crypto-SDR.js',
'InsecurePasswordUtils.jsm',
'LoginAutoCompleteResult.jsm',
'LoginHelper.jsm',
'LoginInfo.jsm',
'LoginManager.jsm',
......
......@@ -107,7 +107,7 @@ var pword = $_(1, "pword");
async function reinitializeForm(index) {
// Using innerHTML is for creating the autocomplete popup again, so the
// preference value will be applied to the constructor of
// UserAutoCompleteResult.
// LoginAutoCompleteResult.
let form = document.getElementById("form" + index);
let temp = form.innerHTML;
form.innerHTML = "";
......
const {UserAutoCompleteResult} = ChromeUtils.import("resource://gre/modules/LoginManagerContent.jsm");
const {LoginAutoCompleteResult} = ChromeUtils.import("resource://gre/modules/LoginAutoCompleteResult.jsm");
var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
......@@ -455,17 +455,16 @@ let expectedResults = [
];
add_task(async function test_all_patterns() {
LoginHelper.createLogger("UserAutoCompleteResult");
LoginHelper.createLogger("LoginAutoCompleteResult");
expectedResults.forEach(pattern => {
Services.prefs.setBoolPref(PREF_INSECURE_FIELD_WARNING_ENABLED,
pattern.insecureFieldWarningEnabled);
Services.prefs.setBoolPref(PREF_INSECURE_AUTOFILLFORMS_ENABLED,
pattern.insecureAutoFillFormsEnabled);
let actual = new UserAutoCompleteResult("", pattern.matchingLogins,
{
isSecure: pattern.isSecure,
isPasswordField: pattern.isPasswordField,
});
let actual = new LoginAutoCompleteResult("", pattern.matchingLogins, {
isSecure: pattern.isSecure,
isPasswordField: pattern.isPasswordField,
});
pattern.items.forEach((item, index) => {
equal(actual.getValueAt(index), item.value);
equal(actual.getLabelAt(index), item.label);
......
......@@ -27,11 +27,11 @@ run-if = buildapp == "browser"
[test_isOriginMatching.js]
[test_legacy_empty_formSubmitURL.js]
[test_legacy_validation.js]
[test_login_autocomplete_result.js]
skip-if = os == "android"
[test_logins_change.js]
[test_logins_decrypt_failure.js]
skip-if = os == "android" # Bug 1171687: Needs fixing on Android
[test_user_autocomplete_result.js]
skip-if = os == "android"
[test_logins_metainfo.js]
[test_logins_search.js]
[test_maybeImportLogin.js]
......
......@@ -125,7 +125,7 @@
"log.js": ["Log"],
"logger.jsm": ["Logger"],
"logging.js": ["getTestLogger", "initTestLogging"],
"LoginManagerContent.jsm": ["LoginManagerContent", "LoginFormFactory", "UserAutoCompleteResult"],
"LoginManagerContent.jsm": ["LoginManagerContent", "LoginFormFactory"],
"LoginRecipes.jsm": ["LoginRecipesContent", "LoginRecipesParent"],
"logmanager.js": ["LogManager"],
"lz4.js": ["Lz4"],
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment