mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
Bug 1567175, support password manager in out of process iframes, r=MattN
Differential Revision: https://phabricator.services.mozilla.com/D47825 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
7cbc074da1
commit
7410901165
@ -20,16 +20,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
|
||||
});
|
||||
|
||||
// NOTE: Much of this logic is duplicated in BrowserCLH.js for Android.
|
||||
addMessageListener("PasswordManager:fillForm", function(message) {
|
||||
// intercept if ContextMenu.jsm had sent a plain object for remote targets
|
||||
LoginManagerChild.receiveMessage(message, content);
|
||||
});
|
||||
addMessageListener("PasswordManager:fillGeneratedPassword", function(message) {
|
||||
// forward message to LMC
|
||||
LoginManagerChild.receiveMessage(message, content);
|
||||
});
|
||||
|
||||
function shouldIgnoreLoginManagerEvent(event) {
|
||||
let nodePrincipal = event.target.nodePrincipal;
|
||||
// If we have a system or null principal then prevent any more password manager code from running and
|
||||
@ -45,13 +35,15 @@ addEventListener("DOMFormBeforeSubmit", function(event) {
|
||||
if (shouldIgnoreLoginManagerEvent(event)) {
|
||||
return;
|
||||
}
|
||||
this.LoginManagerChild.forWindow(content).onDOMFormBeforeSubmit(event);
|
||||
let window = event.target.ownerGlobal;
|
||||
LoginManagerChild.forWindow(window).onDOMFormBeforeSubmit(event);
|
||||
});
|
||||
addEventListener("DOMFormHasPassword", function(event) {
|
||||
if (shouldIgnoreLoginManagerEvent(event)) {
|
||||
return;
|
||||
}
|
||||
this.LoginManagerChild.forWindow(content).onDOMFormHasPassword(event);
|
||||
let window = event.target.ownerGlobal;
|
||||
LoginManagerChild.forWindow(window).onDOMFormHasPassword(event);
|
||||
let formLike = LoginFormFactory.createFromForm(event.originalTarget);
|
||||
InsecurePasswordUtils.reportInsecurePasswords(formLike);
|
||||
});
|
||||
@ -59,10 +51,8 @@ addEventListener("DOMInputPasswordAdded", function(event) {
|
||||
if (shouldIgnoreLoginManagerEvent(event)) {
|
||||
return;
|
||||
}
|
||||
this.LoginManagerChild.forWindow(content).onDOMInputPasswordAdded(
|
||||
event,
|
||||
content
|
||||
);
|
||||
let window = event.target.ownerGlobal;
|
||||
LoginManagerChild.forWindow(window).onDOMInputPasswordAdded(event, content);
|
||||
let formLike = LoginFormFactory.createFromField(event.originalTarget);
|
||||
InsecurePasswordUtils.reportInsecurePasswords(formLike);
|
||||
});
|
||||
|
@ -532,7 +532,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AboutLoginsParent: "resource:///modules/AboutLoginsParent.jsm",
|
||||
AsyncPrefs: "resource://gre/modules/AsyncPrefs.jsm",
|
||||
ContentClick: "resource:///modules/ContentClick.jsm",
|
||||
LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
|
||||
PluginManager: "resource:///actors/PluginParent.jsm",
|
||||
PictureInPicture: "resource://gre/modules/PictureInPicture.jsm",
|
||||
ReaderParent: "resource:///modules/ReaderParent.jsm",
|
||||
@ -596,8 +595,6 @@ const listeners = {
|
||||
"update-error": ["UpdateListener"],
|
||||
"gmp-plugin-crash": ["PluginManager"],
|
||||
"plugin-crashed": ["PluginManager"],
|
||||
"passwordmgr-storage-changed": ["LoginManagerParent"],
|
||||
"passwordmgr-autosaved-login-merged": ["LoginManagerParent"],
|
||||
},
|
||||
|
||||
ppmm: {
|
||||
@ -643,15 +640,6 @@ const listeners = {
|
||||
"PictureInPicture:OpenToggleContextMenu": ["PictureInPicture"],
|
||||
"Reader:FaviconRequest": ["ReaderParent"],
|
||||
"Reader:UpdateReaderButton": ["ReaderParent"],
|
||||
// PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
|
||||
"PasswordManager:findLogins": ["LoginManagerParent"],
|
||||
"PasswordManager:findRecipes": ["LoginManagerParent"],
|
||||
"PasswordManager:onFormSubmit": ["LoginManagerParent"],
|
||||
"PasswordManager:onGeneratedPasswordFilledOrEdited": ["LoginManagerParent"],
|
||||
"PasswordManager:autoCompleteLogins": ["LoginManagerParent"],
|
||||
"PasswordManager:removeLogin": ["LoginManagerParent"],
|
||||
"PasswordManager:OpenPreferences": ["LoginManagerParent"],
|
||||
// PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
|
||||
"rtcpeer:CancelRequest": ["webrtcUI"],
|
||||
"rtcpeer:Request": ["webrtcUI"],
|
||||
"webrtc:CancelRequest": ["webrtcUI"],
|
||||
|
@ -1903,12 +1903,10 @@ var gPrivacyPane = {
|
||||
*/
|
||||
showPasswords() {
|
||||
if (LoginHelper.managementURI) {
|
||||
window.docShell.messageManager.sendAsyncMessage(
|
||||
"PasswordManager:OpenPreferences",
|
||||
{
|
||||
entryPoint: "preferences",
|
||||
}
|
||||
);
|
||||
let loginManager = window.getWindowGlobalChild().getActor("LoginManager");
|
||||
loginManager.sendAsyncMessage("PasswordManager:OpenPreferences", {
|
||||
entryPoint: "preferences",
|
||||
});
|
||||
return;
|
||||
}
|
||||
Services.telemetry.recordEvent("pwmgr", "open_management", "preferences");
|
||||
|
@ -23,11 +23,6 @@ ChromeUtils.defineModuleGetter(
|
||||
"Readerable",
|
||||
"resource://gre/modules/Readerable.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"LoginManagerChild",
|
||||
"resource://gre/modules/LoginManagerChild.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() {
|
||||
return Services.strings.createBundle(
|
||||
@ -639,8 +634,4 @@ var AboutReaderListener = {
|
||||
};
|
||||
AboutReaderListener.init();
|
||||
|
||||
addMessageListener("PasswordManager:fillForm", function(message) {
|
||||
LoginManagerChild.receiveMessage(message, content);
|
||||
});
|
||||
|
||||
Services.obs.notifyObservers(this, "tab-content-frameloader-created");
|
||||
|
@ -75,17 +75,6 @@ BrowserCLH.prototype = {
|
||||
|
||||
GeckoViewUtils.addLazyGetter(this, "LoginManagerParent", {
|
||||
module: "resource://gre/modules/LoginManagerParent.jsm",
|
||||
mm: [
|
||||
// PLEASE KEEP THIS LIST IN SYNC WITH THE DESKTOP LIST IN
|
||||
// BrowserGlue.jsm
|
||||
"PasswordManager:findLogins",
|
||||
"PasswordManager:findRecipes",
|
||||
"PasswordManager:onFormSubmit",
|
||||
"PasswordManager:autoCompleteLogins",
|
||||
"PasswordManager:removeLogin",
|
||||
// PLEASE KEEP THIS LIST IN SYNC WITH THE DESKTOP LIST IN
|
||||
// BrowserGlue.jsm
|
||||
],
|
||||
});
|
||||
GeckoViewUtils.addLazyGetter(this, "LoginManagerChild", {
|
||||
module: "resource://gre/modules/LoginManagerChild.jsm",
|
||||
@ -254,17 +243,6 @@ BrowserCLH.prototype = {
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
aWindow.addEventListener(
|
||||
"pageshow",
|
||||
event => {
|
||||
// XXXbz what about non-HTML documents??
|
||||
if (ChromeUtils.getClassName(event.target) == "HTMLDocument") {
|
||||
this.LoginManagerChild.forWindow(aWindow).onPageShow(event);
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
// QI
|
||||
|
@ -152,9 +152,16 @@ this.InsecurePasswordUtils = {
|
||||
let isLocalIP = this._isPrincipalForLocalIPAddress(
|
||||
aForm.rootElement.nodePrincipal
|
||||
);
|
||||
let topWindow = aForm.ownerDocument.defaultView.top;
|
||||
// XXXndeakin fix this: bug 1582499 - top document not accessible in OOP frame
|
||||
// So for now, just use the current document if access to top fails.
|
||||
let topDocument;
|
||||
try {
|
||||
topDocument = aForm.ownerDocument.defaultView.top.document;
|
||||
} catch (ex) {
|
||||
topDocument = aForm.ownerDocument.defaultView.document;
|
||||
}
|
||||
let topIsLocalIP = this._isPrincipalForLocalIPAddress(
|
||||
topWindow.document.nodePrincipal
|
||||
topDocument.nodePrincipal
|
||||
);
|
||||
|
||||
// Only consider the page safe if the top window has a local IP address
|
||||
|
@ -146,12 +146,12 @@ class LoginAutocompleteItem extends AutocompleteItem {
|
||||
isPasswordField,
|
||||
dateAndTimeFormatter,
|
||||
duplicateUsernames,
|
||||
messageManager,
|
||||
actor,
|
||||
isOriginMatched
|
||||
) {
|
||||
super(SHOULD_SHOW_ORIGIN ? "loginWithOrigin" : "login");
|
||||
this._login = login.QueryInterface(Ci.nsILoginMetaInfo);
|
||||
this._messageManager = messageManager;
|
||||
this._actor = actor;
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "label", () => {
|
||||
let username = login.username;
|
||||
@ -184,9 +184,9 @@ class LoginAutocompleteItem extends AutocompleteItem {
|
||||
}
|
||||
|
||||
removeFromStorage() {
|
||||
if (this._messageManager) {
|
||||
if (this._actor) {
|
||||
let vanilla = LoginHelper.loginToVanillaObject(this._login);
|
||||
this._messageManager.sendAsyncMessage("PasswordManager:removeLogin", {
|
||||
this._actor.sendAsyncMessage("PasswordManager:removeLogin", {
|
||||
login: vanilla,
|
||||
});
|
||||
} else {
|
||||
@ -223,7 +223,7 @@ function LoginAutoCompleteResult(
|
||||
aSearchString,
|
||||
matchingLogins,
|
||||
formOrigin,
|
||||
{ generatedPassword, isSecure, messageManager, isPasswordField, hostname }
|
||||
{ generatedPassword, isSecure, actor, isPasswordField, hostname }
|
||||
) {
|
||||
let hidingFooterOnPWFieldAutoOpened = false;
|
||||
function isFooterEnabled() {
|
||||
@ -280,7 +280,7 @@ function LoginAutoCompleteResult(
|
||||
isPasswordField,
|
||||
dateAndTimeFormatter,
|
||||
duplicateUsernames,
|
||||
messageManager,
|
||||
actor,
|
||||
LoginHelper.isOriginMatching(login.origin, formOrigin, {
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
})
|
||||
@ -438,9 +438,11 @@ LoginAutoComplete.prototype = {
|
||||
let isPasswordField = aElement.type == "password";
|
||||
let hostname = aElement.ownerDocument.documentURIObject.host;
|
||||
|
||||
let loginManagerActor = LoginManagerChild.forWindow(aElement.ownerGlobal);
|
||||
|
||||
let completeSearch = (
|
||||
autoCompleteLookupPromise,
|
||||
{ generatedPassword, logins, messageManager }
|
||||
{ generatedPassword, logins }
|
||||
) => {
|
||||
// If the search was canceled before we got our
|
||||
// results, don't bother reporting them.
|
||||
@ -457,7 +459,7 @@ LoginAutoComplete.prototype = {
|
||||
formOrigin,
|
||||
{
|
||||
generatedPassword,
|
||||
messageManager,
|
||||
actor: loginManagerActor,
|
||||
isSecure,
|
||||
isPasswordField,
|
||||
hostname,
|
||||
@ -505,8 +507,7 @@ LoginAutoComplete.prototype = {
|
||||
previousResult = null;
|
||||
}
|
||||
|
||||
let loginManager = LoginManagerChild.forWindow(aElement.ownerGlobal);
|
||||
let acLookupPromise = (this._autoCompleteLookupPromise = loginManager._autoCompleteSearchAsync(
|
||||
let acLookupPromise = (this._autoCompleteLookupPromise = loginManagerActor._autoCompleteSearchAsync(
|
||||
aSearchString,
|
||||
previousResult,
|
||||
aElement
|
||||
|
@ -22,11 +22,6 @@ ChromeUtils.defineModuleGetter(
|
||||
"LoginFormFactory",
|
||||
"resource://gre/modules/LoginFormFactory.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"LoginManagerChild",
|
||||
"resource://gre/modules/LoginManagerChild.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"InsecurePasswordUtils",
|
||||
|
@ -88,15 +88,11 @@ Services.cpmm.addMessageListener("clearRecipeCache", () => {
|
||||
LoginRecipesContent._clearRecipeCache();
|
||||
});
|
||||
|
||||
let gLoginManagerChildSingleton = null;
|
||||
|
||||
let _messages = [
|
||||
"PasswordManager:loginsFound",
|
||||
"PasswordManager:loginsAutoCompleted",
|
||||
];
|
||||
|
||||
let gLastRightClickTimeStamp = Number.NEGATIVE_INFINITY;
|
||||
|
||||
// Input element on which enter keydown event was fired.
|
||||
let gKeyDownEnterForInput = null;
|
||||
|
||||
const observer = {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
@ -318,10 +314,7 @@ let gAutoCompleteListener = {
|
||||
}
|
||||
|
||||
case "FormAutoComplete:PopupClosed": {
|
||||
this.onPopupClosed(
|
||||
data.selectedRowStyle,
|
||||
target.docShell.messageManager
|
||||
);
|
||||
this.onPopupClosed(data.selectedRowStyle, target);
|
||||
let { chromeEventHandler } = target.docShell;
|
||||
chromeEventHandler.removeEventListener("keydown", this, true);
|
||||
break;
|
||||
@ -345,7 +338,7 @@ let gAutoCompleteListener = {
|
||||
this.keyDownEnterForInput = focusedElement;
|
||||
},
|
||||
|
||||
onPopupClosed(selectedRowStyle, mm) {
|
||||
onPopupClosed(selectedRowStyle, window) {
|
||||
let focusedElement = gFormFillService.focusedInput;
|
||||
let eventTarget = this.keyDownEnterForInput;
|
||||
if (
|
||||
@ -356,16 +349,20 @@ let gAutoCompleteListener = {
|
||||
this.keyDownEnterForInput = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let loginManager = window.getWindowGlobalChild().getActor("LoginManager");
|
||||
let hostname = eventTarget.ownerDocument.documentURIObject.host;
|
||||
mm.sendAsyncMessage("PasswordManager:OpenPreferences", {
|
||||
loginManager.sendAsyncMessage("PasswordManager:OpenPreferences", {
|
||||
hostname,
|
||||
entryPoint: "autocomplete",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
this.LoginManagerChild = class LoginManagerChild {
|
||||
this.LoginManagerChild = class LoginManagerChild extends JSWindowActorChild {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* WeakMap of the root element of a LoginForm to the DeferredTask to fill its fields.
|
||||
*
|
||||
@ -393,70 +390,15 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
* frames, to the current state used by the Login Manager.
|
||||
*/
|
||||
this._loginFormStateByDocument = new WeakMap();
|
||||
|
||||
// Map from form login requests to information about that request.
|
||||
this._requests = new Map();
|
||||
|
||||
// Number of outstanding requests to each manager.
|
||||
this._managers = new Map();
|
||||
}
|
||||
|
||||
static forWindow(window) {
|
||||
// For now, this is a singleton.
|
||||
if (!gLoginManagerChildSingleton) {
|
||||
gLoginManagerChildSingleton = new LoginManagerChild();
|
||||
}
|
||||
return gLoginManagerChildSingleton;
|
||||
}
|
||||
|
||||
_getRandomId() {
|
||||
return Cc["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Ci.nsIUUIDGenerator)
|
||||
.generateUUID()
|
||||
.toString();
|
||||
}
|
||||
|
||||
_takeRequest(msg) {
|
||||
let data = msg.data;
|
||||
let request = this._requests.get(data.requestId);
|
||||
|
||||
this._requests.delete(data.requestId);
|
||||
|
||||
let count = this._managers.get(msg.target);
|
||||
if (--count === 0) {
|
||||
this._managers.delete(msg.target);
|
||||
|
||||
for (let message of _messages) {
|
||||
msg.target.removeMessageListener(message, this);
|
||||
}
|
||||
} else {
|
||||
this._managers.set(msg.target, count);
|
||||
let windowGlobal = window.getWindowGlobalChild();
|
||||
if (windowGlobal) {
|
||||
return windowGlobal.getActor("LoginManager");
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
_sendRequest(messageManager, requestData, name, messageData) {
|
||||
let count;
|
||||
if (!(count = this._managers.get(messageManager))) {
|
||||
this._managers.set(messageManager, 1);
|
||||
|
||||
for (let message of _messages) {
|
||||
messageManager.addMessageListener(message, this);
|
||||
}
|
||||
} else {
|
||||
this._managers.set(messageManager, ++count);
|
||||
}
|
||||
|
||||
let requestId = this._getRandomId();
|
||||
messageData.requestId = requestId;
|
||||
|
||||
messageManager.sendAsyncMessage(name, messageData);
|
||||
|
||||
let deferred = PromiseUtils.defer();
|
||||
requestData.promise = deferred;
|
||||
this._requests.set(requestId, requestData);
|
||||
return deferred.promise;
|
||||
return null;
|
||||
}
|
||||
|
||||
_compareAndUpdatePreviouslySentValues(
|
||||
@ -500,37 +442,14 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
}
|
||||
|
||||
receiveMessage(msg) {
|
||||
if (msg.name == "PasswordManager:fillForm") {
|
||||
this.fillForm({
|
||||
topDocument: msg.target.content.document,
|
||||
loginFormOrigin: msg.data.loginFormOrigin,
|
||||
loginsFound: LoginHelper.vanillaObjectsToLogins(msg.data.logins),
|
||||
recipes: msg.data.recipes,
|
||||
inputElementIdentifier: msg.data.inputElementIdentifier,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.name) {
|
||||
case "PasswordManager:loginsFound": {
|
||||
let loginsFound = LoginHelper.vanillaObjectsToLogins(msg.data.logins);
|
||||
let request = this._takeRequest(msg);
|
||||
request.promise.resolve({
|
||||
form: request.form,
|
||||
loginsFound,
|
||||
case "PasswordManager:fillForm": {
|
||||
this.fillForm({
|
||||
loginFormOrigin: msg.data.loginFormOrigin,
|
||||
loginsFound: LoginHelper.vanillaObjectsToLogins(msg.data.logins),
|
||||
recipes: msg.data.recipes,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "PasswordManager:loginsAutoCompleted": {
|
||||
let loginsFound = LoginHelper.vanillaObjectsToLogins(msg.data.logins);
|
||||
let messageManager = msg.target;
|
||||
let request = this._takeRequest(msg);
|
||||
request.promise.resolve({
|
||||
generatedPassword: msg.data.generatedPassword,
|
||||
logins: loginsFound,
|
||||
messageManager,
|
||||
inputElementIdentifier: msg.data.inputElementIdentifier,
|
||||
originMatches: msg.data.originMatches,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -548,11 +467,11 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
msg.data.password
|
||||
);
|
||||
this.fillForm({
|
||||
topDocument: msg.target.content.document,
|
||||
loginFormOrigin: msg.data.origin,
|
||||
loginsFound: [generatedLogin],
|
||||
recipes: msg.data.recipes,
|
||||
inputElementIdentifier: msg.data.inputElementIdentifier,
|
||||
originMatches: msg.data.originMatches,
|
||||
});
|
||||
let inputElement = ContentDOMReference.resolve(
|
||||
msg.data.inputElementIdentifier
|
||||
@ -564,7 +483,22 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "PasswordManager:formIsPending": {
|
||||
return this._visibleTasksByDocument.has(this.document);
|
||||
}
|
||||
|
||||
case "PasswordManager:formProcessed": {
|
||||
this.notifyObserversOfFormProcessed(msg.data.formid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
notifyObserversOfFormProcessed(formid) {
|
||||
Services.obs.notifyObservers(this, "passwordmgr-processed-form", formid);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -577,41 +511,36 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
*/
|
||||
_getLoginDataFromParent(form, options) {
|
||||
let doc = form.ownerDocument;
|
||||
let win = doc.defaultView;
|
||||
|
||||
let formOrigin = LoginHelper.getLoginOrigin(doc.documentURI);
|
||||
if (!formOrigin) {
|
||||
return Promise.reject(
|
||||
"_getLoginDataFromParent: A form origin is required"
|
||||
);
|
||||
throw new Error("_getLoginDataFromParent: A form origin is required");
|
||||
}
|
||||
let actionOrigin = LoginHelper.getFormActionOrigin(form);
|
||||
|
||||
let messageManager = win.docShell.messageManager;
|
||||
|
||||
// XXX Weak??
|
||||
let requestData = { form };
|
||||
let messageData = { formOrigin, actionOrigin, options };
|
||||
|
||||
return this._sendRequest(
|
||||
messageManager,
|
||||
requestData,
|
||||
let resultPromise = this.sendQuery(
|
||||
"PasswordManager:findLogins",
|
||||
messageData
|
||||
);
|
||||
return resultPromise.then(result => {
|
||||
return {
|
||||
form,
|
||||
loginsFound: LoginHelper.vanillaObjectsToLogins(result.logins),
|
||||
recipes: result.recipes,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
_autoCompleteSearchAsync(aSearchString, aPreviousResult, aElement) {
|
||||
let doc = aElement.ownerDocument;
|
||||
let form = LoginFormFactory.createFromField(aElement);
|
||||
let win = doc.defaultView;
|
||||
|
||||
let formOrigin = LoginHelper.getLoginOrigin(doc.documentURI);
|
||||
let actionOrigin = LoginHelper.getFormActionOrigin(form);
|
||||
let autocompleteInfo = aElement.getAutocompleteInfo();
|
||||
|
||||
let messageManager = win.docShell.messageManager;
|
||||
|
||||
let previousResult = aPreviousResult
|
||||
? {
|
||||
searchString: aPreviousResult.searchString,
|
||||
@ -619,10 +548,8 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
}
|
||||
: null;
|
||||
|
||||
let requestData = {};
|
||||
let messageData = {
|
||||
autocompleteInfo,
|
||||
browsingContextId: win.docShell.browsingContext.id,
|
||||
formOrigin,
|
||||
actionOrigin,
|
||||
searchString: aSearchString,
|
||||
@ -635,12 +562,17 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
gAutoCompleteListener.init();
|
||||
}
|
||||
|
||||
return this._sendRequest(
|
||||
messageManager,
|
||||
requestData,
|
||||
let resultPromise = this.sendQuery(
|
||||
"PasswordManager:autoCompleteLogins",
|
||||
messageData
|
||||
);
|
||||
|
||||
return resultPromise.then(result => {
|
||||
return {
|
||||
generatedPassword: result.generatedPassword,
|
||||
logins: LoginHelper.vanillaObjectsToLogins(result.logins),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
setupProgressListener(window) {
|
||||
@ -858,10 +790,6 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
* @param {LoginForm} form to fetch the logins for then try autofill.
|
||||
*/
|
||||
_fetchLoginsFromParentAndFillForm(form) {
|
||||
let window = form.ownerDocument.defaultView;
|
||||
let messageManager = window.docShell.messageManager;
|
||||
messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
|
||||
|
||||
if (!LoginHelper.enabled) {
|
||||
return;
|
||||
}
|
||||
@ -900,10 +828,6 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
*
|
||||
* @param An object with the following properties:
|
||||
* {
|
||||
* topDocument:
|
||||
* DOM document currently associated to the the top-level window
|
||||
* for which the fill is requested. This may be different from the
|
||||
* document that originally caused the login UI to be displayed.
|
||||
* loginFormOrigin:
|
||||
* String with the origin for which the login UI was displayed.
|
||||
* This must match the origin of the form used for the fill.
|
||||
@ -916,14 +840,16 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
* Fill recipes transmitted together with the original message.
|
||||
* inputElementIdentifier:
|
||||
* An identifier generated for the input element via ContentDOMReference.
|
||||
* originMatches:
|
||||
* True if the origin of the form matches the page URI.
|
||||
* }
|
||||
*/
|
||||
fillForm({
|
||||
topDocument,
|
||||
loginFormOrigin,
|
||||
loginsFound,
|
||||
recipes,
|
||||
inputElementIdentifier,
|
||||
originMatches,
|
||||
}) {
|
||||
if (!inputElementIdentifier) {
|
||||
log("fillForm: No input element specified");
|
||||
@ -938,9 +864,7 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
LoginHelper.getLoginOrigin(topDocument.documentURI) != loginFormOrigin
|
||||
) {
|
||||
if (!originMatches) {
|
||||
if (
|
||||
!inputElement ||
|
||||
LoginHelper.getLoginOrigin(inputElement.ownerDocument.documentURI) !=
|
||||
@ -1061,7 +985,11 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
let acForm = LoginFormFactory.createFromField(acInputField);
|
||||
let doc = acForm.ownerDocument;
|
||||
let formOrigin = LoginHelper.getLoginOrigin(doc.documentURI);
|
||||
let recipes = LoginRecipesContent.getRecipes(formOrigin, doc.defaultView);
|
||||
let recipes = LoginRecipesContent.getRecipes(
|
||||
this,
|
||||
formOrigin,
|
||||
doc.defaultView
|
||||
);
|
||||
|
||||
// Make sure the username field fillForm will use is the
|
||||
// same field as the autocomplete was activated on.
|
||||
@ -1467,9 +1395,8 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
}
|
||||
|
||||
let formActionOrigin = LoginHelper.getFormActionOrigin(form);
|
||||
let messageManager = win.docShell.messageManager;
|
||||
|
||||
let recipes = LoginRecipesContent.getRecipes(origin, win);
|
||||
let recipes = LoginRecipesContent.getRecipes(this, origin, win);
|
||||
|
||||
// Get the appropriate fields from the form.
|
||||
let [
|
||||
@ -1559,7 +1486,7 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
let autoFilledLogin = this.stateForDocument(doc).fillsByRootElement.get(
|
||||
form.rootElement
|
||||
);
|
||||
messageManager.sendAsyncMessage("PasswordManager:onFormSubmit", {
|
||||
this.sendAsyncMessage("PasswordManager:onFormSubmit", {
|
||||
origin,
|
||||
formActionOrigin,
|
||||
autoFilledLoginGuid: autoFilledLogin && autoFilledLogin.guid,
|
||||
@ -1639,7 +1566,7 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
let origin = LoginHelper.getLoginOrigin(
|
||||
passwordField.ownerDocument.documentURI
|
||||
);
|
||||
let recipes = LoginRecipesContent.getRecipes(origin, win);
|
||||
let recipes = LoginRecipesContent.getRecipes(this, origin, win);
|
||||
let [usernameField] = this._getFormFields(loginForm, false, recipes);
|
||||
let username = (usernameField && usernameField.value) || "";
|
||||
// Avoid prompting twice for the same value,
|
||||
@ -1662,17 +1589,12 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
if (win.opener) {
|
||||
openerTopWindowID = win.opener.top.windowUtils.outerWindowID;
|
||||
}
|
||||
let messageManager = win.docShell.messageManager;
|
||||
messageManager.sendAsyncMessage(
|
||||
"PasswordManager:onGeneratedPasswordFilledOrEdited",
|
||||
{
|
||||
browsingContextId: win.docShell.browsingContext.id,
|
||||
formActionOrigin,
|
||||
openerTopWindowID,
|
||||
password: passwordField.value,
|
||||
username,
|
||||
}
|
||||
);
|
||||
this.sendAsyncMessage("PasswordManager:onGeneratedPasswordFilledOrEdited", {
|
||||
formActionOrigin,
|
||||
openerTopWindowID,
|
||||
password: passwordField.value,
|
||||
username: (usernameField && usernameField.value) || "",
|
||||
});
|
||||
}
|
||||
|
||||
_togglePasswordFieldMasking(passwordField, unmask) {
|
||||
@ -2107,10 +2029,6 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
|
||||
log("_fillForm succeeded");
|
||||
autofillResult = AUTOFILL_RESULT.FILLED;
|
||||
|
||||
let win = doc.defaultView;
|
||||
let messageManager = win.docShell.messageManager;
|
||||
messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
throw ex;
|
||||
@ -2146,10 +2064,9 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
usernameField.addEventListener("mousedown", observer);
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(
|
||||
form.rootElement,
|
||||
"passwordmgr-processed-form"
|
||||
);
|
||||
this.sendAsyncMessage("PasswordManager:formProcessed", {
|
||||
formid: form.rootElement.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -2231,7 +2148,11 @@ this.LoginManagerChild = class LoginManagerChild {
|
||||
|
||||
let doc = aField.ownerDocument;
|
||||
let formOrigin = LoginHelper.getLoginOrigin(doc.documentURI);
|
||||
let recipes = LoginRecipesContent.getRecipes(formOrigin, doc.defaultView);
|
||||
let recipes = LoginRecipesContent.getRecipes(
|
||||
this,
|
||||
formOrigin,
|
||||
doc.defaultView
|
||||
);
|
||||
|
||||
return this._getFormFields(form, false, recipes);
|
||||
}
|
||||
|
@ -102,9 +102,18 @@ this.LoginManagerContextMenu = {
|
||||
},
|
||||
|
||||
async fillGeneratedPassword(inputElementIdentifier, documentURI, browser) {
|
||||
let password = LoginManagerParent.getGeneratedPassword(
|
||||
inputElementIdentifier.browsingContextId
|
||||
);
|
||||
let browsingContextId = inputElementIdentifier.browsingContextId;
|
||||
let browsingContext = BrowsingContext.get(browsingContextId);
|
||||
if (!browsingContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
let actor = browsingContext.currentWindowGlobal.getActor("LoginManager");
|
||||
if (!actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let password = actor.getGeneratedPassword();
|
||||
let origin = LoginHelper.getLoginOrigin(documentURI.spec);
|
||||
log.debug("fillGeneratedPassword into:", inputElementIdentifier, origin);
|
||||
|
||||
@ -118,15 +127,17 @@ this.LoginManagerContextMenu = {
|
||||
// Some schemes e.g. chrome aren't supported by URL
|
||||
log.debug("Couldnt get recipes for formHost:", formHost, ex);
|
||||
}
|
||||
browser.messageManager.sendAsyncMessage(
|
||||
"PasswordManager:fillGeneratedPassword",
|
||||
{
|
||||
password,
|
||||
origin,
|
||||
inputElementIdentifier,
|
||||
recipes,
|
||||
}
|
||||
);
|
||||
|
||||
let browserURI = browser.browsingContext.currentWindowGlobal.documentURI;
|
||||
let originMatches = LoginHelper.getLoginOrigin(browserURI) == origin;
|
||||
|
||||
actor.sendAsyncMessage("PasswordManager:fillGeneratedPassword", {
|
||||
password,
|
||||
origin,
|
||||
originMatches,
|
||||
inputElementIdentifier,
|
||||
recipes,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -211,7 +222,18 @@ this.LoginManagerContextMenu = {
|
||||
* origin when subframes are involved.
|
||||
*/
|
||||
_fillTargetField(login, inputElementIdentifier, browser, formOrigin) {
|
||||
LoginManagerParent.getLoginManagerParent()
|
||||
let browsingContextId = inputElementIdentifier.browsingContextId;
|
||||
let browsingContext = BrowsingContext.get(browsingContextId);
|
||||
if (!browsingContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
let actor = browsingContext.currentWindowGlobal.getActor("LoginManager");
|
||||
if (!actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
actor
|
||||
.fillForm({
|
||||
browser,
|
||||
inputElementIdentifier,
|
||||
|
@ -17,11 +17,6 @@ const LoginInfo = new Components.Constructor(
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"DeferredTask",
|
||||
"resource://gre/modules/DeferredTask.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"LoginHelper",
|
||||
@ -52,7 +47,10 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
||||
|
||||
const EXPORTED_SYMBOLS = ["LoginManagerParent"];
|
||||
|
||||
let gLoginManagerParentSingleton = null;
|
||||
/**
|
||||
* A listener for notifications to tests.
|
||||
*/
|
||||
let gListenerForTests = null;
|
||||
|
||||
/**
|
||||
* A map of a principal's origin (including suffixes) to a generated password string and filled flag
|
||||
@ -73,11 +71,49 @@ let gGeneratedPasswordsByPrincipalOrigin = new Map();
|
||||
*/
|
||||
let gRecipeManager = null;
|
||||
|
||||
class LoginManagerParent {
|
||||
constructor() {
|
||||
// Tracks the last time the user cancelled the master password prompt,
|
||||
// to avoid spamming master password prompts on autocomplete searches.
|
||||
this._lastMPLoginCancelled = Math.NEGATIVE_INFINITY;
|
||||
/**
|
||||
* Tracks the last time the user cancelled the master password prompt,
|
||||
* to avoid spamming master password prompts on autocomplete searches.
|
||||
*/
|
||||
let gLastMPLoginCancelled = Math.NEGATIVE_INFINITY;
|
||||
|
||||
let gGeneratedPasswordObserver = {
|
||||
addedObserver: false,
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (
|
||||
topic == "passwordmgr-autosaved-login-merged" ||
|
||||
(topic == "passwordmgr-storage-changed" && data == "removeLogin")
|
||||
) {
|
||||
let { origin, guid } = subject;
|
||||
let generatedPW = gGeneratedPasswordsByPrincipalOrigin.get(origin);
|
||||
|
||||
// in the case where an autosaved login removed or merged into an existing login,
|
||||
// clear the guid associated with the generated-password cache entry
|
||||
if (
|
||||
generatedPW &&
|
||||
(guid == generatedPW.storageGUID ||
|
||||
topic == "passwordmgr-autosaved-login-merged")
|
||||
) {
|
||||
log(
|
||||
"Removing storageGUID for generated-password cache entry on origin:",
|
||||
origin
|
||||
);
|
||||
generatedPW.storageGUID = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Services.ppmm.addMessageListener("PasswordManager:findRecipes", message => {
|
||||
let formHost = new URL(message.data.formOrigin).host;
|
||||
return gRecipeManager.getRecipesForHost(formHost);
|
||||
});
|
||||
|
||||
class LoginManagerParent extends JSWindowActorParent {
|
||||
// This is used by tests to listen to form submission.
|
||||
static setListenerForTests(listener) {
|
||||
gListenerForTests = listener;
|
||||
}
|
||||
|
||||
// Some unit tests need to access this.
|
||||
@ -85,12 +121,14 @@ class LoginManagerParent {
|
||||
return gGeneratedPasswordsByPrincipalOrigin;
|
||||
}
|
||||
|
||||
static getLoginManagerParent() {
|
||||
// For now, this is a singleton.
|
||||
if (!gLoginManagerParentSingleton) {
|
||||
gLoginManagerParentSingleton = new LoginManagerParent();
|
||||
getRootBrowser() {
|
||||
let browsingContext = null;
|
||||
if (this._overrideBrowsingContextId) {
|
||||
browsingContext = BrowsingContext.get(this._overrideBrowsingContextId);
|
||||
} else {
|
||||
browsingContext = this.browsingContext.top;
|
||||
}
|
||||
return gLoginManagerParentSingleton;
|
||||
return browsingContext.embedderElement;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +168,7 @@ class LoginManagerParent {
|
||||
// to avoid spamming them with MP prompts for autocomplete.
|
||||
if (e.result == Cr.NS_ERROR_ABORT) {
|
||||
log("User cancelled master password prompt.");
|
||||
this._lastMPLoginCancelled = Date.now();
|
||||
gLastMPLoginCancelled = Date.now();
|
||||
return [];
|
||||
}
|
||||
throw e;
|
||||
@ -153,35 +191,25 @@ class LoginManagerParent {
|
||||
);
|
||||
}
|
||||
|
||||
static receiveMessage(msg) {
|
||||
LoginManagerParent.getLoginManagerParent().receiveMessage(msg);
|
||||
}
|
||||
|
||||
// Listeners are added in BrowserGlue.jsm on desktop
|
||||
// and in BrowserCLH.js on mobile.
|
||||
receiveMessage(msg) {
|
||||
let data = msg.data;
|
||||
switch (msg.name) {
|
||||
case "PasswordManager:findLogins": {
|
||||
// TODO Verify msg.target's principals against the formOrigin?
|
||||
this.sendLoginDataToChild(
|
||||
// TODO Verify the target's principals against the formOrigin?
|
||||
return this.sendLoginDataToChild(
|
||||
data.formOrigin,
|
||||
data.actionOrigin,
|
||||
data.requestId,
|
||||
msg.target.messageManager,
|
||||
data.options
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "PasswordManager:findRecipes": {
|
||||
let formHost = new URL(data.formOrigin).host;
|
||||
return gRecipeManager.getRecipesForHost(formHost);
|
||||
}
|
||||
|
||||
case "PasswordManager:onFormSubmit": {
|
||||
// TODO Verify msg.target's principals against the formOrigin?
|
||||
this.onFormSubmit(msg.target, data);
|
||||
let browser = this.getRootBrowser();
|
||||
this.onFormSubmit(browser, data);
|
||||
if (gListenerForTests) {
|
||||
gListenerForTests("FormSubmit", data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -191,8 +219,7 @@ class LoginManagerParent {
|
||||
}
|
||||
|
||||
case "PasswordManager:autoCompleteLogins": {
|
||||
this.doAutocompleteSearch(data, msg.target);
|
||||
break;
|
||||
return this.doAutocompleteSearch(data);
|
||||
}
|
||||
|
||||
case "PasswordManager:removeLogin": {
|
||||
@ -202,42 +229,33 @@ class LoginManagerParent {
|
||||
}
|
||||
|
||||
case "PasswordManager:OpenPreferences": {
|
||||
LoginHelper.openPasswordManager(msg.target.ownerGlobal, {
|
||||
let window = this.getRootBrowser().ownerGlobal;
|
||||
LoginHelper.openPasswordManager(window, {
|
||||
filterString: msg.data.hostname,
|
||||
entryPoint: msg.data.entryPoint,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Used by tests to detect that a form-fill has occurred. This redirects
|
||||
// to the top-level browsing context.
|
||||
case "PasswordManager:formProcessed": {
|
||||
let topActor = this.browsingContext.top.currentWindowGlobal.getActor(
|
||||
"LoginManager"
|
||||
);
|
||||
topActor.sendAsyncMessage("PasswordManager:formProcessed", {
|
||||
formid: data.formid,
|
||||
});
|
||||
if (gListenerForTests) {
|
||||
gListenerForTests("FormProcessed", {});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Observers are added in BrowserGlue.jsm on desktop
|
||||
observe(subject, topic, data) {
|
||||
if (
|
||||
topic == "passwordmgr-autosaved-login-merged" ||
|
||||
(topic == "passwordmgr-storage-changed" && data == "removeLogin")
|
||||
) {
|
||||
let { origin, guid } = subject;
|
||||
let generatedPW = gGeneratedPasswordsByPrincipalOrigin.get(origin);
|
||||
|
||||
// in the case where an autosaved login removed or merged into an existing login,
|
||||
// clear the guid associated with the generated-password cache entry
|
||||
if (
|
||||
generatedPW &&
|
||||
(guid == generatedPW.storageGUID ||
|
||||
topic == "passwordmgr-autosaved-login-merged")
|
||||
) {
|
||||
log(
|
||||
"Removing storageGUID for generated-password cache entry on origin:",
|
||||
origin
|
||||
);
|
||||
generatedPW.storageGUID = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a login form fill and send relevant data (e.g. logins and recipes)
|
||||
* to the child process (LoginManagerChild).
|
||||
@ -259,9 +277,14 @@ class LoginManagerParent {
|
||||
// doesn't support structured cloning.
|
||||
let jsLogins = [LoginHelper.loginToVanillaObject(login)];
|
||||
|
||||
browser.messageManager.sendAsyncMessage("PasswordManager:fillForm", {
|
||||
let browserURI = browser.currentURI.spec;
|
||||
let originMatches =
|
||||
LoginHelper.getLoginOrigin(browserURI) == loginFormOrigin;
|
||||
|
||||
this.sendAsyncMessage("PasswordManager:fillForm", {
|
||||
inputElementIdentifier,
|
||||
loginFormOrigin,
|
||||
originMatches,
|
||||
logins: jsLogins,
|
||||
recipes,
|
||||
});
|
||||
@ -273,8 +296,6 @@ class LoginManagerParent {
|
||||
async sendLoginDataToChild(
|
||||
formOrigin,
|
||||
actionOrigin,
|
||||
requestId,
|
||||
target,
|
||||
{ guid, showMasterPassword }
|
||||
) {
|
||||
let recipes = [];
|
||||
@ -290,22 +311,19 @@ class LoginManagerParent {
|
||||
}
|
||||
|
||||
if (!showMasterPassword && !Services.logins.isLoggedIn) {
|
||||
try {
|
||||
target.sendAsyncMessage("PasswordManager:loginsFound", {
|
||||
requestId,
|
||||
logins: [],
|
||||
recipes,
|
||||
});
|
||||
} catch (e) {
|
||||
log("error sending message to target", e);
|
||||
}
|
||||
return;
|
||||
return { logins: [], recipes };
|
||||
}
|
||||
|
||||
// If we're currently displaying a master password prompt, defer
|
||||
// processing this form until the user handles the prompt.
|
||||
if (Services.logins.uiBusy) {
|
||||
log("deferring sendLoginDataToChild for", formOrigin);
|
||||
|
||||
let uiBusyPromiseResolve;
|
||||
let uiBusyPromise = new Promise(resolve => {
|
||||
uiBusyPromiseResolve = resolve;
|
||||
});
|
||||
|
||||
let self = this;
|
||||
let observer = {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
@ -319,23 +337,14 @@ class LoginManagerParent {
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
|
||||
if (topic == "passwordmgr-crypto-loginCanceled") {
|
||||
target.sendAsyncMessage("PasswordManager:loginsFound", {
|
||||
requestId,
|
||||
logins: [],
|
||||
recipes,
|
||||
});
|
||||
uiBusyPromise.resolve({ logins: [], recipes });
|
||||
return;
|
||||
}
|
||||
|
||||
self.sendLoginDataToChild(
|
||||
formOrigin,
|
||||
actionOrigin,
|
||||
requestId,
|
||||
target,
|
||||
{
|
||||
showMasterPassword,
|
||||
}
|
||||
);
|
||||
let result = self.sendLoginDataToChild(formOrigin, actionOrigin, {
|
||||
showMasterPassword,
|
||||
});
|
||||
uiBusyPromiseResolve(result);
|
||||
},
|
||||
};
|
||||
|
||||
@ -346,7 +355,8 @@ class LoginManagerParent {
|
||||
// See bug XXX.
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-login");
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled");
|
||||
return;
|
||||
|
||||
return uiBusyPromise;
|
||||
}
|
||||
|
||||
// Autocomplete results do not need to match actionOrigin or exact origin.
|
||||
@ -367,50 +377,34 @@ class LoginManagerParent {
|
||||
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
|
||||
// doesn't support structured cloning.
|
||||
let jsLogins = LoginHelper.loginsToVanillaObjects(logins);
|
||||
target.sendAsyncMessage("PasswordManager:loginsFound", {
|
||||
requestId,
|
||||
logins: jsLogins,
|
||||
recipes,
|
||||
});
|
||||
return { logins: jsLogins, recipes };
|
||||
}
|
||||
|
||||
doAutocompleteSearch(
|
||||
{
|
||||
autocompleteInfo,
|
||||
browsingContextId,
|
||||
formOrigin,
|
||||
actionOrigin,
|
||||
searchString,
|
||||
previousResult,
|
||||
requestId,
|
||||
isSecure,
|
||||
isPasswordField,
|
||||
},
|
||||
target
|
||||
) {
|
||||
doAutocompleteSearch({
|
||||
autocompleteInfo,
|
||||
formOrigin,
|
||||
actionOrigin,
|
||||
searchString,
|
||||
previousResult,
|
||||
isSecure,
|
||||
isPasswordField,
|
||||
}) {
|
||||
// Note: previousResult is a regular object, not an
|
||||
// nsIAutoCompleteResult.
|
||||
|
||||
// Cancel if we unsuccessfully prompted for the master password too recently.
|
||||
if (!Services.logins.isLoggedIn) {
|
||||
let timeDiff = Date.now() - this._lastMPLoginCancelled;
|
||||
if (timeDiff < this._repromptTimeout) {
|
||||
let timeDiff = Date.now() - gLastMPLoginCancelled;
|
||||
if (timeDiff < LoginManagerParent._repromptTimeout) {
|
||||
log(
|
||||
"Not searching logins for autocomplete since the master password " +
|
||||
`prompt was last cancelled ${Math.round(
|
||||
timeDiff / 1000
|
||||
)} seconds ago.`
|
||||
);
|
||||
// Send an empty array to make LoginManagerChild clear the
|
||||
// Return an empty array to make LoginManagerChild clear the
|
||||
// outstanding request it has temporarily saved.
|
||||
target.messageManager.sendAsyncMessage(
|
||||
"PasswordManager:loginsAutoCompleted",
|
||||
{
|
||||
requestId,
|
||||
logins: [],
|
||||
}
|
||||
);
|
||||
return;
|
||||
return { logins: [] };
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,27 +442,22 @@ class LoginManagerParent {
|
||||
return match && match.toLowerCase().startsWith(searchStringLower);
|
||||
});
|
||||
|
||||
let browser = this.getRootBrowser();
|
||||
|
||||
let generatedPassword = null;
|
||||
if (
|
||||
isPasswordField &&
|
||||
autocompleteInfo.fieldName == "new-password" &&
|
||||
Services.logins.getLoginSavingEnabled(formOrigin) &&
|
||||
!PrivateBrowsingUtils.isWindowPrivate(target.ownerGlobal)
|
||||
(!browser || !PrivateBrowsingUtils.isWindowPrivate(browser.ownerGlobal))
|
||||
) {
|
||||
generatedPassword = this.getGeneratedPassword(browsingContextId);
|
||||
generatedPassword = this.getGeneratedPassword();
|
||||
}
|
||||
|
||||
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
|
||||
// doesn't support structured cloning.
|
||||
let jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
|
||||
target.messageManager.sendAsyncMessage(
|
||||
"PasswordManager:loginsAutoCompleted",
|
||||
{
|
||||
requestId,
|
||||
generatedPassword,
|
||||
logins: jsLogins,
|
||||
}
|
||||
);
|
||||
return { generatedPassword, logins: jsLogins };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -478,7 +467,20 @@ class LoginManagerParent {
|
||||
return BrowsingContext;
|
||||
}
|
||||
|
||||
getGeneratedPassword(browsingContextId) {
|
||||
// Set an override context within a test.
|
||||
useBrowsingContext(browsingContextId = 0) {
|
||||
this._overrideBrowsingContextId = browsingContextId;
|
||||
}
|
||||
|
||||
getBrowsingContextToUse() {
|
||||
if (this._overrideBrowsingContextId) {
|
||||
return BrowsingContext.get(this._overrideBrowsingContextId);
|
||||
}
|
||||
|
||||
return this.browsingContext;
|
||||
}
|
||||
|
||||
getGeneratedPassword() {
|
||||
if (
|
||||
!LoginHelper.enabled ||
|
||||
!LoginHelper.generationAvailable ||
|
||||
@ -487,7 +489,7 @@ class LoginManagerParent {
|
||||
return null;
|
||||
}
|
||||
|
||||
let browsingContext = BrowsingContext.get(browsingContextId);
|
||||
let browsingContext = this.getBrowsingContextToUse();
|
||||
if (!browsingContext) {
|
||||
return null;
|
||||
}
|
||||
@ -514,6 +516,20 @@ class LoginManagerParent {
|
||||
storageGUID: null,
|
||||
value: PasswordGenerator.generatePassword(),
|
||||
};
|
||||
|
||||
// Add these observers when a password is assigned.
|
||||
if (!gGeneratedPasswordObserver.addedObserver) {
|
||||
Services.obs.addObserver(
|
||||
gGeneratedPasswordObserver,
|
||||
"passwordmgr-autosaved-login-merged"
|
||||
);
|
||||
Services.obs.addObserver(
|
||||
gGeneratedPasswordObserver,
|
||||
"passwordmgr-storage-changed"
|
||||
);
|
||||
gGeneratedPasswordObserver.addedObserver = true;
|
||||
}
|
||||
|
||||
gGeneratedPasswordsByPrincipalOrigin.set(framePrincipalOrigin, generatedPW);
|
||||
return generatedPW.value;
|
||||
}
|
||||
@ -721,7 +737,6 @@ class LoginManagerParent {
|
||||
}
|
||||
|
||||
_onGeneratedPasswordFilledOrEdited({
|
||||
browsingContextId,
|
||||
formActionOrigin,
|
||||
openerTopWindowID,
|
||||
password,
|
||||
@ -729,12 +744,20 @@ class LoginManagerParent {
|
||||
}) {
|
||||
log("_onGeneratedPasswordFilledOrEdited");
|
||||
|
||||
if (gListenerForTests) {
|
||||
gListenerForTests("PasswordFilledOrEdited", {});
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
log("_onGeneratedPasswordFilledOrEdited: The password field is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
let browsingContext = BrowsingContext.get(browsingContextId);
|
||||
let browsingContext = this.getBrowsingContextToUse();
|
||||
if (!browsingContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
let {
|
||||
originNoSuffix,
|
||||
} = browsingContext.currentWindowGlobal.documentPrincipal;
|
||||
@ -901,7 +924,7 @@ class LoginManagerParent {
|
||||
"_onGeneratedPasswordFilledOrEdited: not auto-saving/updating this login"
|
||||
);
|
||||
}
|
||||
let browser = browsingContext.top.embedderElement;
|
||||
let browser = this.getRootBrowser();
|
||||
let prompter = this._getPrompter(browser, openerTopWindowID);
|
||||
|
||||
if (loginToChange) {
|
||||
|
@ -245,11 +245,12 @@ this.LoginRecipesContent = {
|
||||
* Tries to fetch recipes for a given host, using a local cache if possible.
|
||||
* Otherwise, the recipes are cached for later use.
|
||||
*
|
||||
* @param {JSWindowActor} aActor - actor making request
|
||||
* @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
|
||||
* @param {Object} win - the window of the host
|
||||
* @return {Set} of recipes that apply to the host
|
||||
*/
|
||||
getRecipes(aHost, win) {
|
||||
getRecipes(aActor, aHost, win) {
|
||||
let recipes;
|
||||
let recipeMap = this._recipeCache.get(win);
|
||||
|
||||
@ -261,10 +262,8 @@ this.LoginRecipesContent = {
|
||||
}
|
||||
}
|
||||
|
||||
let mm = win.docShell.messageManager;
|
||||
|
||||
log.warn("getRecipes: falling back to a synchronous message for:", aHost);
|
||||
recipes = mm.sendSyncMessage("PasswordManager:findRecipes", {
|
||||
recipes = Services.cpmm.sendSyncMessage("PasswordManager:findRecipes", {
|
||||
formOrigin: aHost,
|
||||
})[0];
|
||||
this.cacheRecipes(aHost, win, recipes);
|
||||
|
@ -5,22 +5,6 @@ const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
|
||||
const BASIC_FORM_NO_USERNAME_PAGE_PATH =
|
||||
DIRECTORY_PATH + "form_basic_no_username.html";
|
||||
|
||||
function getSubmitMessage() {
|
||||
info("getSubmitMessage");
|
||||
return new Promise((resolve, reject) => {
|
||||
Services.mm.addMessageListener(
|
||||
"PasswordManager:onFormSubmit",
|
||||
function onFormSubmit() {
|
||||
Services.mm.removeMessageListener(
|
||||
"PasswordManager:onFormSubmit",
|
||||
onFormSubmit
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test() {
|
||||
let nsLoginInfo = new Components.Constructor(
|
||||
"@mozilla.org/login-manager/loginInfo;1",
|
||||
@ -57,7 +41,7 @@ add_task(async function test() {
|
||||
|
||||
// Convert the login object to a plain JS object for passing across process boundaries.
|
||||
login = LoginHelper.loginToVanillaObject(login);
|
||||
ContentTask.spawn(
|
||||
await ContentTask.spawn(
|
||||
tab.linkedBrowser,
|
||||
{ login, usernameRequested },
|
||||
async ({ login: addedLogin, usernameRequested: aUsernameRequested }) => {
|
||||
@ -95,7 +79,7 @@ add_task(async function test() {
|
||||
}
|
||||
);
|
||||
|
||||
let processedPromise = getSubmitMessage();
|
||||
let processedPromise = listenForTestNotification("FormSubmit");
|
||||
ContentTask.spawn(tab.linkedBrowser, null, () => {
|
||||
content.document.getElementById("form-basic").submit();
|
||||
});
|
||||
|
@ -32,12 +32,15 @@ add_task(async function test_initialize() {
|
||||
});
|
||||
|
||||
add_task(async function test_context_menu_username() {
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: TEST_ORIGIN + BASIC_FORM_PAGE_PATH,
|
||||
},
|
||||
async function(browser) {
|
||||
await formFilled;
|
||||
await openContextMenu(browser, "#form-basic-username");
|
||||
|
||||
let contextMenu = document.getElementById("contentAreaContextMenu");
|
||||
@ -48,12 +51,15 @@ add_task(async function test_context_menu_username() {
|
||||
});
|
||||
|
||||
add_task(async function test_context_menu_password() {
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: TEST_ORIGIN + BASIC_FORM_PAGE_PATH,
|
||||
},
|
||||
async function(browser) {
|
||||
await formFilled;
|
||||
await openContextMenu(browser, "#form-basic-password");
|
||||
|
||||
let contextMenu = document.getElementById("contentAreaContextMenu");
|
||||
|
@ -234,6 +234,8 @@ add_task(async function fill_generated_password_with_matching_logins() {
|
||||
Services.logins.addLogin(login);
|
||||
await storageChangedPromised;
|
||||
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
@ -241,6 +243,7 @@ add_task(async function fill_generated_password_with_matching_logins() {
|
||||
},
|
||||
async function(browser) {
|
||||
await SimpleTest.promiseFocus(browser.ownerGlobal);
|
||||
await formFilled;
|
||||
await ContentTask.spawn(
|
||||
browser,
|
||||
[passwordInputSelector],
|
||||
|
@ -3,22 +3,6 @@
|
||||
const TEST_HOSTNAME = "https://example.com";
|
||||
const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
|
||||
|
||||
function getSubmitMessage() {
|
||||
info("getSubmitMessage");
|
||||
return new Promise((resolve, reject) => {
|
||||
Services.mm.addMessageListener(
|
||||
"PasswordManager:onFormSubmit",
|
||||
function onFormSubmit() {
|
||||
Services.mm.removeMessageListener(
|
||||
"PasswordManager:onFormSubmit",
|
||||
onFormSubmit
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_doorhanger_dismissal_un() {
|
||||
let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
|
||||
await BrowserTestUtils.withNewTab(
|
||||
@ -31,8 +15,8 @@ add_task(async function test_doorhanger_dismissal_un() {
|
||||
// the password field is a three digit numberic value,
|
||||
// we automatically dismiss the save logins prompt on submission.
|
||||
|
||||
let processedPromise = getSubmitMessage();
|
||||
ContentTask.spawn(browser, null, async () => {
|
||||
let processedPromise = listenForTestNotification("FormSubmit");
|
||||
await ContentTask.spawn(browser, null, async () => {
|
||||
content.document
|
||||
.getElementById("form-basic-username")
|
||||
.setUserInput("4111111111111111");
|
||||
@ -64,8 +48,8 @@ add_task(async function test_doorhanger_dismissal_pw() {
|
||||
// the password field is also tagged autocomplete="cc-number",
|
||||
// we automatically dismiss the save logins prompt on submission.
|
||||
|
||||
let processedPromise = getSubmitMessage();
|
||||
ContentTask.spawn(browser, null, async () => {
|
||||
let processedPromise = listenForTestNotification("FormSubmit");
|
||||
await ContentTask.spawn(browser, null, async () => {
|
||||
content.document
|
||||
.getElementById("form-basic-username")
|
||||
.setUserInput("aaa");
|
||||
@ -99,8 +83,8 @@ add_task(async function test_doorhanger_shown_on_un_with_invalid_ccnumber() {
|
||||
// If the username field has a CC number that is invalid,
|
||||
// we show the doorhanger to save logins like we usually do.
|
||||
|
||||
let processedPromise = getSubmitMessage();
|
||||
ContentTask.spawn(browser, null, async () => {
|
||||
let processedPromise = listenForTestNotification("FormSubmit");
|
||||
await ContentTask.spawn(browser, null, async () => {
|
||||
content.document.getElementById("form-basic-username").value =
|
||||
"1234123412341234";
|
||||
content.document.getElementById("form-basic-password").value = "411";
|
||||
@ -144,8 +128,8 @@ add_task(async function test_doorhanger_dismissal_on_change() {
|
||||
);
|
||||
Services.logins.addLogin(login);
|
||||
|
||||
let processedPromise = getSubmitMessage();
|
||||
ContentTask.spawn(browser, null, async () => {
|
||||
let processedPromise = listenForTestNotification("FormSubmit");
|
||||
await ContentTask.spawn(browser, null, async () => {
|
||||
content.document
|
||||
.getElementById("form-basic-password")
|
||||
.setUserInput("111");
|
||||
|
@ -130,6 +130,8 @@ async function verifyConfirmationHint(hintElem) {
|
||||
}
|
||||
|
||||
async function openFormInNewTab(url, formValues, taskFn) {
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
@ -137,6 +139,8 @@ async function openFormInNewTab(url, formValues, taskFn) {
|
||||
},
|
||||
async function(browser) {
|
||||
await SimpleTest.promiseFocus(browser.ownerGlobal);
|
||||
await formFilled;
|
||||
|
||||
await ContentTask.spawn(
|
||||
browser,
|
||||
formValues,
|
||||
|
@ -11,39 +11,6 @@ async function getDocumentVisibilityState(browser) {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
async function addContentObserver(browser, topic) {
|
||||
// add an observer.
|
||||
await ContentTask.spawn(browser, [topic], function(contentTopic) {
|
||||
this.gObserver = {
|
||||
wasObserved: false,
|
||||
observe: () => {
|
||||
content.wasObserved = true;
|
||||
},
|
||||
};
|
||||
Services.obs.addObserver(this.gObserver, contentTopic);
|
||||
});
|
||||
}
|
||||
|
||||
async function getContentObserverResult(browser, topic) {
|
||||
let result = await ContentTask.spawn(browser, [topic], async function(
|
||||
contentTopic
|
||||
) {
|
||||
const { TestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
try {
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return content.wasObserved;
|
||||
}, `Wait for "passwordmgr-processed-form"`);
|
||||
} catch (ex) {
|
||||
content.wasObserved = false;
|
||||
}
|
||||
Services.obs.removeObserver(this.gObserver, "passwordmgr-processed-form");
|
||||
return content.wasObserved;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Waits for the master password prompt and cancels it.
|
||||
function observeMasterPasswordDialog(window, result) {
|
||||
let closedPromise;
|
||||
@ -94,16 +61,9 @@ add_task(async function test_processed_form_fired() {
|
||||
let tab1Visibility = await getDocumentVisibilityState(tab1.linkedBrowser);
|
||||
is(tab1Visibility, "visible", "The first tab should be foreground");
|
||||
|
||||
await addContentObserver(tab1.linkedBrowser, "passwordmgr-processed-form");
|
||||
let formProcessedPromise = listenForTestNotification("FormProcessed");
|
||||
await BrowserTestUtils.loadURI(tab1.linkedBrowser, FORM_URL);
|
||||
let result = await getContentObserverResult(
|
||||
tab1.linkedBrowser,
|
||||
"passwordmgr-processed-form"
|
||||
);
|
||||
ok(
|
||||
result,
|
||||
"Observer should be notified when form is loaded into a visible document"
|
||||
);
|
||||
await formProcessedPromise;
|
||||
gBrowser.removeTab(tab1);
|
||||
});
|
||||
|
||||
@ -124,26 +84,33 @@ testUrls.forEach(testUrl => {
|
||||
tab1Visibility = await getDocumentVisibilityState(tab1.linkedBrowser);
|
||||
is(tab1Visibility, "hidden", "The first tab should be backgrounded");
|
||||
|
||||
// we shouldn't even try to autofill while hidden, so look for the passwordmgr-processed-form
|
||||
// to be observed rather than any result of filling the form
|
||||
await addContentObserver(tab1.linkedBrowser, "passwordmgr-processed-form");
|
||||
// we shouldn't even try to autofill while hidden, so wait for the document to be in the
|
||||
// non-visible pending queue instead.
|
||||
let formFilled = false;
|
||||
listenForTestNotification("FormProcessed").then(() => {
|
||||
formFilled = true;
|
||||
});
|
||||
await BrowserTestUtils.loadURI(tab1.linkedBrowser, testUrl);
|
||||
result = await getContentObserverResult(
|
||||
tab1.linkedBrowser,
|
||||
"passwordmgr-processed-form"
|
||||
);
|
||||
|
||||
await TestUtils.waitForCondition(() => {
|
||||
let windowGlobal = tab1.linkedBrowser.browsingContext.currentWindowGlobal;
|
||||
if (!windowGlobal || windowGlobal.documentURI.spec == "about:blank") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let actor = windowGlobal.getActor("LoginManager");
|
||||
return actor.sendQuery("PasswordManager:formIsPending");
|
||||
});
|
||||
|
||||
ok(
|
||||
!result,
|
||||
!formFilled,
|
||||
"Observer should not be notified when form is loaded into a hidden document"
|
||||
);
|
||||
|
||||
// Add the observer before switching tab
|
||||
await addContentObserver(tab1.linkedBrowser, "passwordmgr-processed-form");
|
||||
let formProcessedPromise = listenForTestNotification("FormProcessed");
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab1);
|
||||
result = await getContentObserverResult(
|
||||
tab1.linkedBrowser,
|
||||
"passwordmgr-processed-form"
|
||||
);
|
||||
result = await formProcessedPromise;
|
||||
tab1Visibility = await getDocumentVisibilityState(tab1.linkedBrowser);
|
||||
is(tab1Visibility, "visible", "The first tab should be foreground");
|
||||
ok(
|
||||
@ -219,14 +186,11 @@ add_task(async function test_immediate_autofill_with_masterpassword() {
|
||||
|
||||
// In this case we will try to autofill while hidden, so look for the passwordmgr-processed-form
|
||||
// to be observed
|
||||
await addContentObserver(tab1.linkedBrowser, "passwordmgr-processed-form");
|
||||
let formProcessedPromise = listenForTestNotification("FormProcessed");
|
||||
await BrowserTestUtils.loadURI(tab1.linkedBrowser, FORM_URL);
|
||||
let wasProcessed = getContentObserverResult(
|
||||
tab1.linkedBrowser,
|
||||
"passwordmgr-processed-form"
|
||||
);
|
||||
await Promise.all([dialogObserved, wasProcessed]);
|
||||
await Promise.all([formProcessedPromise, dialogObserved]);
|
||||
|
||||
let wasProcessed = await formProcessedPromise;
|
||||
ok(
|
||||
wasProcessed,
|
||||
"Observer should be notified when form is loaded into a hidden document"
|
||||
|
@ -23,14 +23,18 @@ add_task(async function setup() {
|
||||
|
||||
add_task(async function test_http_autofill() {
|
||||
for (let scheme of ["http", "https"]) {
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
`${scheme}${TEST_URL_PATH}form_basic.html`
|
||||
);
|
||||
|
||||
let [username, password] = await ContentTask.spawn(
|
||||
await formFilled;
|
||||
|
||||
let [username, password] = await SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
null,
|
||||
[],
|
||||
async function() {
|
||||
let doc = content.document;
|
||||
let contentUsername = doc.getElementById("form-basic-username").value;
|
||||
@ -56,24 +60,25 @@ add_task(async function test_http_autofill() {
|
||||
|
||||
add_task(async function test_iframe_in_http_autofill() {
|
||||
for (let scheme of ["http", "https"]) {
|
||||
// Wait for parent and child iframe to be processed.
|
||||
let formFilled = listenForTestNotification("FormProcessed", 2);
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
`${scheme}${TEST_URL_PATH}form_basic_iframe.html`
|
||||
);
|
||||
|
||||
let [username, password] = await ContentTask.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
null,
|
||||
await formFilled;
|
||||
|
||||
let [username, password] = await SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser.browsingContext.getChildren()[0],
|
||||
[],
|
||||
async function() {
|
||||
let doc = content.document;
|
||||
let iframe = doc.getElementById("test-iframe");
|
||||
let contentUsername = iframe.contentWindow.document.getElementById(
|
||||
"form-basic-username"
|
||||
).value;
|
||||
let contentPassword = iframe.contentWindow.document.getElementById(
|
||||
"form-basic-password"
|
||||
).value;
|
||||
return [contentUsername, contentPassword];
|
||||
let doc = this.content.document;
|
||||
return [
|
||||
doc.getElementById("form-basic-username").value,
|
||||
doc.getElementById("form-basic-password").value,
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
@ -94,19 +99,24 @@ add_task(async function test_iframe_in_http_autofill() {
|
||||
|
||||
add_task(async function test_http_action_autofill() {
|
||||
for (let type of ["insecure", "secure"]) {
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
`https${TEST_URL_PATH}form_cross_origin_${type}_action.html`
|
||||
);
|
||||
|
||||
let [username, password] = await ContentTask.spawn(
|
||||
await formFilled;
|
||||
|
||||
let [username, password] = await SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
null,
|
||||
[],
|
||||
async function() {
|
||||
let doc = content.document;
|
||||
let contentUsername = doc.getElementById("form-basic-username").value;
|
||||
let contentPassword = doc.getElementById("form-basic-password").value;
|
||||
return [contentUsername, contentPassword];
|
||||
let doc = this.content.document;
|
||||
return [
|
||||
doc.getElementById("form-basic-username").value,
|
||||
doc.getElementById("form-basic-password").value,
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -46,7 +46,11 @@ function synthesizeDblClickOnCell(aTree, column, row) {
|
||||
);
|
||||
}
|
||||
|
||||
async function togglePasswords() {
|
||||
async function togglePasswords(promptWillShow) {
|
||||
let confirmPromptDone = promptWillShow
|
||||
? BrowserTestUtils.waitForEvent(pwmgrdlg, "endmodalstate")
|
||||
: Promise.resolve();
|
||||
|
||||
pwmgrdlg.document.querySelector("#togglePasswords").doCommand();
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => !signonsTree.columns.getNamedColumn("passwordCol").hidden,
|
||||
@ -54,6 +58,7 @@ async function togglePasswords() {
|
||||
);
|
||||
await new Promise(resolve => waitForFocus(resolve, pwmgrdlg));
|
||||
pwmgrdlg.document.documentElement.clientWidth; // flush to ensure UI up-to-date
|
||||
await confirmPromptDone;
|
||||
}
|
||||
|
||||
async function editUsernamePromises(site, oldUsername, newUsername) {
|
||||
@ -166,9 +171,9 @@ add_task(async function test_edit_multiple_logins() {
|
||||
) {
|
||||
addLogin(site, oldUsername, oldPassword);
|
||||
await editUsernamePromises(site, oldUsername, newUsername);
|
||||
await togglePasswords();
|
||||
await togglePasswords(true);
|
||||
await editPasswordPromises(site, oldPassword, newPassword);
|
||||
await togglePasswords();
|
||||
await togglePasswords(false);
|
||||
}
|
||||
|
||||
await testLoginChange(
|
||||
|
@ -572,13 +572,7 @@ add_task(async function test_normal_autofilled_7() {
|
||||
},
|
||||
async function(browser) {
|
||||
// Add the observer before loading the form page
|
||||
let formFilled = ContentTask.spawn(browser, null, async function() {
|
||||
const { TestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
await TestUtils.topicObserved("passwordmgr-processed-form");
|
||||
await Promise.resolve();
|
||||
});
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
await SimpleTest.promiseFocus(browser.ownerGlobal);
|
||||
await BrowserTestUtils.loadURI(browser, form1Url);
|
||||
await formFilled;
|
||||
@ -600,6 +594,8 @@ add_task(async function test_private_not_autofilled_8() {
|
||||
// Sanity check the HTTP login exists.
|
||||
is(Services.logins.getAllLogins().length, 1, "Should have the HTTP login");
|
||||
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
await focusWindow(privateWin);
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
@ -607,6 +603,7 @@ add_task(async function test_private_not_autofilled_8() {
|
||||
url: form1Url,
|
||||
},
|
||||
async function(browser) {
|
||||
await formFilled;
|
||||
let fieldValues = await submitFormAndGetResults(
|
||||
browser,
|
||||
"formsubmit.sjs",
|
||||
@ -667,6 +664,8 @@ add_task(async function test_normal_autofilled_10() {
|
||||
// Sanity check the HTTP login exists.
|
||||
is(Services.logins.getAllLogins().length, 1, "Should have the HTTP login");
|
||||
|
||||
let formFilled = listenForTestNotification("FormProcessed");
|
||||
|
||||
await focusWindow(normalWin);
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
@ -674,6 +673,7 @@ add_task(async function test_normal_autofilled_10() {
|
||||
url: form1Url,
|
||||
},
|
||||
async function(browser) {
|
||||
await formFilled;
|
||||
let fieldValues = await submitFormAndGetResults(
|
||||
browser,
|
||||
"formsubmit.sjs",
|
||||
|
@ -38,7 +38,9 @@ async function showChangePasswordDoorhanger(
|
||||
formLogin,
|
||||
{ notificationType = "password-change", autoSavedLoginGuid = "" } = {}
|
||||
) {
|
||||
let prompter = LoginManagerParent._getPrompter(browser, null);
|
||||
let windowGlobal = browser.browsingContext.currentWindowGlobal;
|
||||
let loginManagerActor = windowGlobal.getActor("LoginManager");
|
||||
let prompter = loginManagerActor._getPrompter(browser, null);
|
||||
ok(!PopupNotifications.isPanelOpen, "Check the doorhanger isnt already open");
|
||||
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(
|
||||
|
@ -561,6 +561,30 @@ async function openPasswordContextMenu(
|
||||
await popupShownPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for the login manager test notification specified by
|
||||
* expectedMessage. Possible messages:
|
||||
* FormProcessed - a form was processed after page load.
|
||||
* FormSubmit - a form was just submitted.
|
||||
* PasswordFilledOrEdited - a password was filled in or modified.
|
||||
*
|
||||
* The count is the number of that messages to wait for. This should
|
||||
* typically be used when waiting for the FormProcessed message for a page
|
||||
* that has subframes to ensure all have been handled.
|
||||
*
|
||||
* Returns a promise that will passed additional data specific to the message.
|
||||
*/
|
||||
function listenForTestNotification(expectedMessage, count = 1) {
|
||||
return new Promise(resolve => {
|
||||
LoginManagerParent.setListenerForTests((msg, data) => {
|
||||
if (msg == expectedMessage && --count == 0) {
|
||||
LoginManagerParent.setListenerForTests(null);
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the contextmenu to fill a field with a generated password
|
||||
*/
|
||||
@ -595,21 +619,12 @@ async function doFillGeneratedPasswordContextMenuItem(browser, passwordInput) {
|
||||
await ContentTaskUtils.waitForEvent(input, "input");
|
||||
}
|
||||
);
|
||||
let messagePromise = new Promise(resolve => {
|
||||
const eventName = "PasswordManager:onGeneratedPasswordFilledOrEdited";
|
||||
browser.messageManager.addMessageListener(eventName, function mgsHandler(
|
||||
msg
|
||||
) {
|
||||
if (msg.target != browser) {
|
||||
return;
|
||||
}
|
||||
browser.messageManager.removeMessageListener(eventName, mgsHandler);
|
||||
info(
|
||||
"doFillGeneratedPasswordContextMenuItem: Got onGeneratedPasswordFilledOrEdited, resolving"
|
||||
);
|
||||
// allow LMP to handle the message, then resolve
|
||||
SimpleTest.executeSoon(resolve);
|
||||
});
|
||||
|
||||
let passwordGeneratedPromise = listenForTestNotification(
|
||||
"PasswordFilledOrEdited"
|
||||
);
|
||||
await new Promise(resolve => {
|
||||
SimpleTest.executeSoon(resolve);
|
||||
});
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(generatedPasswordItem, {});
|
||||
@ -617,5 +632,5 @@ async function doFillGeneratedPasswordContextMenuItem(browser, passwordInput) {
|
||||
"doFillGeneratedPasswordContextMenuItem: Waiting for content input event"
|
||||
);
|
||||
await passwordChangedPromise;
|
||||
await messagePromise;
|
||||
await passwordGeneratedPromise;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ module.exports = {
|
||||
"assert": true,
|
||||
"addMessageListener": true,
|
||||
"sendAsyncMessage": true,
|
||||
|
||||
"Assert": true,
|
||||
},
|
||||
"rules": {
|
||||
"no-var": "off",
|
||||
|
@ -108,6 +108,11 @@ function checkAutoCompleteResults(actualValues, expectedValues, hostname, msg) {
|
||||
checkArrayValues(actualValues.slice(0, -1), expectedValues, msg);
|
||||
}
|
||||
|
||||
function getIframeBrowsingContext(window, iframeNumber = 0) {
|
||||
let bc = SpecialPowers.wrap(window).getWindowGlobalChild().browsingContext;
|
||||
return SpecialPowers.unwrap(bc.getChildren()[iframeNumber]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for expected username/password in form.
|
||||
* @see `checkForm` below for a similar function.
|
||||
@ -131,6 +136,44 @@ function checkLoginForm(
|
||||
);
|
||||
}
|
||||
|
||||
function checkLoginFormInChildFrame(
|
||||
iframeBC,
|
||||
usernameFieldId,
|
||||
expectedUsername,
|
||||
passwordFieldId,
|
||||
expectedPassword
|
||||
) {
|
||||
return SpecialPowers.spawn(
|
||||
iframeBC,
|
||||
[usernameFieldId, expectedUsername, passwordFieldId, expectedPassword],
|
||||
(
|
||||
usernameFieldIdF,
|
||||
expectedUsernameF,
|
||||
passwordFieldIdF,
|
||||
expectedPasswordF
|
||||
) => {
|
||||
let usernameField = this.content.document.getElementById(
|
||||
usernameFieldIdF
|
||||
);
|
||||
let passwordField = this.content.document.getElementById(
|
||||
passwordFieldIdF
|
||||
);
|
||||
|
||||
let formID = usernameField.parentNode.id;
|
||||
Assert.equal(
|
||||
usernameField.value,
|
||||
expectedUsernameF,
|
||||
"Checking " + formID + " username is: " + expectedUsernameF
|
||||
);
|
||||
Assert.equal(
|
||||
passwordField.value,
|
||||
expectedPasswordF,
|
||||
"Checking " + formID + " password is: " + expectedPasswordF
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a form for expected values. If an argument is null, a field's
|
||||
* expected value will be the default value.
|
||||
@ -228,12 +271,12 @@ function registerRunTests() {
|
||||
form.appendChild(password);
|
||||
|
||||
var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
|
||||
var formLikeRoot = subject;
|
||||
if (formLikeRoot.id !== "observerforcer") {
|
||||
if (data !== "observerforcer") {
|
||||
return;
|
||||
}
|
||||
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
|
||||
formLikeRoot.remove();
|
||||
form.remove();
|
||||
SimpleTest.executeSoon(() => {
|
||||
var runTestEvent = new Event("runTests");
|
||||
window.dispatchEvent(runTestEvent);
|
||||
|
@ -218,14 +218,12 @@ addMessageListener("setMasterPassword", ({ enable }) => {
|
||||
}
|
||||
});
|
||||
|
||||
function onFormSubmit(message) {
|
||||
sendAsyncMessage("formSubmissionProcessed", message.data, message.objects);
|
||||
}
|
||||
|
||||
Services.mm.addMessageListener("PasswordManager:onFormSubmit", onFormSubmit);
|
||||
addMessageListener("cleanup", () => {
|
||||
Services.mm.removeMessageListener(
|
||||
"PasswordManager:onFormSubmit",
|
||||
onFormSubmit
|
||||
);
|
||||
LoginManagerParent.setListenerForTests((msg, data) => {
|
||||
if (msg == "FormSubmit") {
|
||||
sendAsyncMessage("formSubmissionProcessed", data, {});
|
||||
}
|
||||
});
|
||||
|
||||
addMessageListener("cleanup", () => {
|
||||
LoginManagerParent.setListenerForTests(null);
|
||||
});
|
||||
|
@ -54,17 +54,19 @@ runInParent(function addLogins() {
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
|
||||
let iframe = document.getElementsByTagName("iframe")[0];
|
||||
let iframeDoc, hostname;
|
||||
let uname;
|
||||
let pword;
|
||||
|
||||
// Restore the form to the default state.
|
||||
function restoreForm() {
|
||||
pword.focus();
|
||||
uname.value = "";
|
||||
pword.value = "";
|
||||
uname.focus();
|
||||
return SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-password").focus();
|
||||
this.content.document.getElementById("form-basic-username").value = "";
|
||||
this.content.document.getElementById("form-basic-password").value = "";
|
||||
this.content.document.getElementById("form-basic-username").focus();
|
||||
});
|
||||
}
|
||||
|
||||
const HTTP_FORM_URL = "http://example.com/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html";
|
||||
@ -79,10 +81,11 @@ async function setup(formUrl) {
|
||||
}, {once: true});
|
||||
});
|
||||
|
||||
iframeDoc = iframe.contentDocument;
|
||||
hostname = iframeDoc.documentURIObject.host;
|
||||
uname = iframeDoc.getElementById("form-basic-username");
|
||||
pword = iframeDoc.getElementById("form-basic-password");
|
||||
await promiseFormsProcessed();
|
||||
|
||||
hostname = SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
return this.content.document.documentURIObject.host;
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_autocomplete_https_downgrade() {
|
||||
@ -95,9 +98,10 @@ add_task(async function test_autocomplete_https_downgrade() {
|
||||
// from a HTTP page, look for matching logins, we should never offer a login with an HTTPS scheme
|
||||
// we're expecting just login2 as a match
|
||||
// Make sure initial form is empty.
|
||||
checkLoginForm(uname, "", pword, "");
|
||||
await checkLoginFormInChildFrame(iframe, "form-basic-username", "name1", "form-basic-password", "pass1");
|
||||
|
||||
// Trigger autocomplete popup
|
||||
restoreForm();
|
||||
await restoreForm();
|
||||
let popupState = await getPopupState();
|
||||
is(popupState.open, false, "Check popup is initially closed");
|
||||
let shownPromise = promiseACShown();
|
||||
@ -106,7 +110,7 @@ add_task(async function test_autocomplete_https_downgrade() {
|
||||
info("got results: " + results.join(", "));
|
||||
popupState = await getPopupState();
|
||||
is(popupState.selectedIndex, -1, "Check no entries are selected");
|
||||
checkAutoCompleteResults(results, ["name1"], "http://example.com", "initial");
|
||||
checkAutoCompleteResults(results, ["name1", "name2"], hostname, "initial");
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -54,17 +54,17 @@ runInParent(function addLogins() {
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
|
||||
let iframe = document.getElementsByTagName("iframe")[0];
|
||||
let iframeDoc, hostname;
|
||||
let uname;
|
||||
let pword;
|
||||
|
||||
// Restore the form to the default state.
|
||||
function restoreForm() {
|
||||
pword.focus();
|
||||
uname.value = "";
|
||||
pword.value = "";
|
||||
uname.focus();
|
||||
return SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-password").focus();
|
||||
this.content.document.getElementById("form-basic-username").value = "";
|
||||
this.content.document.getElementById("form-basic-password").value = "";
|
||||
this.content.document.getElementById("form-basic-username").focus();
|
||||
});
|
||||
}
|
||||
|
||||
const HTTPS_FORM_URL = "https://example.com/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html";
|
||||
@ -79,10 +79,11 @@ async function setup(formUrl = HTTPS_FORM_URL) {
|
||||
}, {once: true});
|
||||
});
|
||||
|
||||
iframeDoc = iframe.contentDocument;
|
||||
hostname = iframeDoc.documentURIObject.host;
|
||||
uname = iframeDoc.getElementById("form-basic-username");
|
||||
pword = iframeDoc.getElementById("form-basic-password");
|
||||
await promiseFormsProcessed();
|
||||
|
||||
hostname = SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
return this.content.document.documentURIObject.host;
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function setup_https_frame() {
|
||||
@ -91,9 +92,9 @@ add_task(async function setup_https_frame() {
|
||||
|
||||
add_task(async function test_empty_first_entry() {
|
||||
// Make sure initial form is empty.
|
||||
checkLoginForm(uname, "", pword, "");
|
||||
checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "", "form-basic-password", "");
|
||||
// Trigger autocomplete popup
|
||||
restoreForm();
|
||||
await restoreForm();
|
||||
let popupState = await getPopupState();
|
||||
is(popupState.open, false, "Check popup is initially closed");
|
||||
let shownPromise = promiseACShown();
|
||||
@ -107,14 +108,14 @@ add_task(async function test_empty_first_entry() {
|
||||
let index0Promise = notifySelectedIndex(0);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await index0Promise;
|
||||
checkLoginForm(uname, "", pword, ""); // value shouldn't update
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "", "form-basic-password", ""); // value shouldn't update
|
||||
synthesizeKey("KEY_Enter");
|
||||
await promiseFormsProcessed();
|
||||
checkLoginForm(uname, "name", pword, "pass");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "name", "form-basic-password", "pass");
|
||||
});
|
||||
|
||||
add_task(async function test_empty_second_entry() {
|
||||
restoreForm();
|
||||
await restoreForm();
|
||||
let shownPromise = promiseACShown();
|
||||
synthesizeKey("KEY_ArrowDown"); // open
|
||||
await shownPromise;
|
||||
@ -122,16 +123,21 @@ add_task(async function test_empty_second_entry() {
|
||||
synthesizeKey("KEY_ArrowDown"); // second
|
||||
synthesizeKey("KEY_Enter");
|
||||
await promiseFormsProcessed();
|
||||
checkLoginForm(uname, "name1", pword, "pass1");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "name1", "form-basic-password", "pass1");
|
||||
});
|
||||
|
||||
add_task(async function test_search() {
|
||||
restoreForm();
|
||||
await restoreForm();
|
||||
let shownPromise = promiseACShown();
|
||||
|
||||
// We need to blur for the autocomplete controller to notice the forced value below.
|
||||
uname.blur();
|
||||
uname.value = "name";
|
||||
uname.focus();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
let uname = this.content.document.getElementById("form-basic-username");
|
||||
uname.blur();
|
||||
uname.value = "name";
|
||||
uname.focus();
|
||||
});
|
||||
|
||||
sendChar("1");
|
||||
synthesizeKey("KEY_ArrowDown"); // open
|
||||
let results = await shownPromise;
|
||||
@ -139,15 +145,14 @@ add_task(async function test_search() {
|
||||
synthesizeKey("KEY_ArrowDown"); // first
|
||||
synthesizeKey("KEY_Enter");
|
||||
await promiseFormsProcessed();
|
||||
checkLoginForm(uname, "name1", pword, "pass1");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "name1", "form-basic-password", "pass1");
|
||||
|
||||
let popupState = await getPopupState();
|
||||
is(popupState.open, false, "Check popup is now closed");
|
||||
});
|
||||
|
||||
add_task(async function test_delete_first_entry() {
|
||||
restoreForm();
|
||||
uname.focus();
|
||||
await restoreForm();
|
||||
let shownPromise = promiseACShown();
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await shownPromise;
|
||||
@ -161,7 +166,7 @@ add_task(async function test_delete_first_entry() {
|
||||
// On Win/Linux, shift-backspace does not work, delete and shift-delete do.
|
||||
synthesizeKey("KEY_Delete", {shiftKey: true});
|
||||
await deletionPromise;
|
||||
checkLoginForm(uname, "", pword, "");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "", "form-basic-password", "");
|
||||
|
||||
let results = await notifyMenuChanged(3, "name1");
|
||||
|
||||
@ -174,8 +179,7 @@ add_task(async function test_delete_first_entry() {
|
||||
});
|
||||
|
||||
add_task(async function test_delete_duplicate_entry() {
|
||||
restoreForm();
|
||||
uname.focus();
|
||||
await restoreForm();
|
||||
let shownPromise = promiseACShown();
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await shownPromise;
|
||||
@ -189,7 +193,7 @@ add_task(async function test_delete_duplicate_entry() {
|
||||
// On Win/Linux, shift-backspace does not work, delete and shift-delete do.
|
||||
synthesizeKey("KEY_Delete", {shiftKey: true});
|
||||
await deletionPromise;
|
||||
checkLoginForm(uname, "", pword, "");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "", "form-basic-password", "");
|
||||
|
||||
is(await LoginManager.countLogins("http://example.com", "http://example.com", null), 1,
|
||||
"Check that the HTTP login remains");
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!DOCTYPE HTML>
|
||||
xcod<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -11,15 +11,6 @@
|
||||
<body>
|
||||
<script>
|
||||
runChecksAfterCommonInit();
|
||||
|
||||
runInParent(function initLogins() {
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let login1 = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
|
||||
login1.init("https://example.com", "", null, "autofilled", "pass1", "", "");
|
||||
Services.logins.addLogin(login1);
|
||||
});
|
||||
|
||||
</script>
|
||||
<p id="display"></p>
|
||||
|
||||
@ -31,6 +22,20 @@ runInParent(function initLogins() {
|
||||
<script class="testbody" type="text/javascript">
|
||||
let win;
|
||||
|
||||
add_task(async function setup() {
|
||||
let loginAddedPromise = promiseStorageChanged(["addLogin"]);
|
||||
|
||||
runInParent(function initLogins() {
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let login1 = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
|
||||
login1.init("https://example.com", "", null, "autofilled", "pass1", "", "");
|
||||
Services.logins.addLogin(login1);
|
||||
});
|
||||
|
||||
await loginAddedPromise;
|
||||
});
|
||||
|
||||
add_task(async function test_crossOriginBfcacheRestore() {
|
||||
let processedPromise = promiseFormsProcessed();
|
||||
win = window.open("form_basic.html", "loginWin");
|
||||
|
@ -9,6 +9,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<script>
|
||||
let formFilledPromise = promiseFormsProcessed();
|
||||
|
||||
runInParent(function initLogins() {
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
@ -32,16 +34,13 @@ runInParent(function initLogins() {
|
||||
|
||||
<pre id="test">
|
||||
<script>
|
||||
let {ContentTaskUtils} = SpecialPowers.Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
|
||||
|
||||
add_task(async function test_field_highlight_on_autofill() {
|
||||
await formFilledPromise;
|
||||
|
||||
let username = document.getElementById("uname");
|
||||
let password = document.getElementById("pword");
|
||||
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
return document.defaultView.getComputedStyle(username).getPropertyValue("filter") !== "none";
|
||||
}, "Highlight was successfully applied to the username field on page load autofill");
|
||||
|
||||
isnot(document.defaultView.getComputedStyle(username).getPropertyValue("filter"), "none",
|
||||
"Highlight was successfully applied to the username field on page load autofill");
|
||||
isnot(document.defaultView.getComputedStyle(password).getPropertyValue("filter"), "none",
|
||||
"Highlight was successfully applied to the password field on page load autofill");
|
||||
|
||||
|
@ -45,12 +45,15 @@ async function checkFormsWithLogin(formUrls, login, expectedUsername, expectedPa
|
||||
|
||||
await prepareAndProcessForm(url);
|
||||
info("form was processed");
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
info("checking form, uname: " + uname.value);
|
||||
is(uname.value, expectedUsername, `username ${expectedUsername ? "filled" : "not filled"} on ${url}`);
|
||||
is(pword.value, expectedPassword, `password ${expectedPassword ? "filled" : "not filled"} on ${url}`);
|
||||
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [url, expectedUsername, expectedPassword],
|
||||
function(urlContent, expectedUsernameContent, expectedPasswordContent) {
|
||||
let doc = this.content.document;
|
||||
let uname = doc.getElementById("form-basic-username");
|
||||
let pword = doc.getElementById("form-basic-password");
|
||||
Assert.equal(uname.value, expectedUsernameContent, `username ${expectedUsernameContent ? "filled" : "not filled"} on ${urlContent}`);
|
||||
Assert.equal(pword.value, expectedPasswordContent, `password ${expectedPasswordContent ? "filled" : "not filled"} on ${urlContent}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ let nsLoginInfo = SpecialPowers.wrap(SpecialPowers.Components).Constructor("@moz
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
|
||||
let iframe = document.getElementsByTagName("iframe")[0];
|
||||
|
||||
async function prepareLoginsAndProcessForm(url, logins = []) {
|
||||
await LoginManager.removeAllLogins();
|
||||
@ -55,10 +55,9 @@ add_task(async function test_simpleNoDupesNoAction() {
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
checkLoginForm(uname, "name2", pword, "pass2");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0),
|
||||
"form-basic-username", "name2",
|
||||
"form-basic-password", "pass2");
|
||||
});
|
||||
|
||||
add_task(async function test_simpleNoDupesUpgradeOriginAndAction() {
|
||||
@ -67,10 +66,8 @@ add_task(async function test_simpleNoDupesUpgradeOriginAndAction() {
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
checkLoginForm(uname, "name2", pword, "pass2");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "name2",
|
||||
"form-basic-password", "pass2");
|
||||
});
|
||||
|
||||
add_task(async function test_simpleNoDupesUpgradeOriginOnly() {
|
||||
@ -79,10 +76,8 @@ add_task(async function test_simpleNoDupesUpgradeOriginOnly() {
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
checkLoginForm(uname, "name2", pword, "pass2");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "name2",
|
||||
"form-basic-password", "pass2");
|
||||
});
|
||||
|
||||
add_task(async function test_simpleNoDupesUpgradeActionOnly() {
|
||||
@ -91,10 +86,8 @@ add_task(async function test_simpleNoDupesUpgradeActionOnly() {
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
checkLoginForm(uname, "name2", pword, "pass2");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "name2",
|
||||
"form-basic-password", "pass2");
|
||||
});
|
||||
|
||||
add_task(async function test_dedupe() {
|
||||
@ -109,10 +102,8 @@ add_task(async function test_dedupe() {
|
||||
"name1", "passHTTPStoHTTP", "uname", "pword"),
|
||||
]);
|
||||
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
checkLoginForm(uname, "name1", pword, "passHTTPStoHTTPS");
|
||||
await checkLoginFormInChildFrame(getIframeBrowsingContext(window, 0), "form-basic-username", "name1",
|
||||
"form-basic-password", "passHTTPStoHTTPS");
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -47,7 +47,7 @@ runInParent(function addLogins() {
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
|
||||
let iframe = document.getElementsByTagName("iframe")[0];
|
||||
let iframeDoc, hostname;
|
||||
|
||||
add_task(async function setup() {
|
||||
@ -58,8 +58,11 @@ add_task(async function setup() {
|
||||
}, {once: true});
|
||||
});
|
||||
|
||||
iframeDoc = iframe.contentDocument;
|
||||
hostname = iframeDoc.documentURIObject.host;
|
||||
await promiseFormsProcessed();
|
||||
|
||||
hostname = SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
return this.content.document.documentURIObject.host;
|
||||
});
|
||||
|
||||
SimpleTest.requestFlakyTimeout("Giving a chance for the unexpected popupshown to occur");
|
||||
});
|
||||
@ -70,7 +73,9 @@ add_task(async function test_initial_focus() {
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
synthesizeKey("KEY_Enter");
|
||||
await promiseFormsProcessed();
|
||||
is(iframeDoc.getElementById("form-basic-password").value, "pass", "Check first password filled");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
Assert.equal(this.content.document.getElementById("form-basic-password").value, "pass", "Check first password filled");
|
||||
});
|
||||
let popupState = await getPopupState();
|
||||
is(popupState.open, false, "Check popup is now closed");
|
||||
});
|
||||
@ -78,33 +83,47 @@ add_task(async function test_initial_focus() {
|
||||
// This depends on the filling from the previous test.
|
||||
add_task(async function test_not_reopened_if_filled() {
|
||||
listenForUnexpectedPopupShown();
|
||||
let usernameField = iframeDoc.getElementById("form-basic-username");
|
||||
usernameField.focus();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-username").focus();
|
||||
});
|
||||
info("Waiting to see if a popupshown occurs");
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// cleanup
|
||||
gPopupShownExpected = true;
|
||||
iframeDoc.getElementById("form-basic-submit").focus();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-submit").focus();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_reopened_after_edit_not_matching_saved() {
|
||||
let usernameField = iframeDoc.getElementById("form-basic-username");
|
||||
usernameField.value = "nam";
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-username").value = "nam";
|
||||
});
|
||||
let shownPromise = promiseACShown();
|
||||
usernameField.focus();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-username").focus();
|
||||
});
|
||||
await shownPromise;
|
||||
iframeDoc.getElementById("form-basic-submit").focus();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-submit").focus();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_not_reopened_after_selecting() {
|
||||
let formFillController = SpecialPowers.Cc["@mozilla.org/satchel/form-fill-controller;1"].
|
||||
getService(SpecialPowers.Ci.nsIFormFillController);
|
||||
let usernameField = iframeDoc.getElementById("form-basic-username");
|
||||
usernameField.value = "";
|
||||
iframeDoc.getElementById("form-basic-password").value = "";
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
this.content.document.getElementById("form-basic-username").value = "";
|
||||
this.content.document.getElementById("form-basic-password").value = "";
|
||||
});
|
||||
listenForUnexpectedPopupShown();
|
||||
formFillController.markAsLoginManagerField(usernameField);
|
||||
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
|
||||
let formFillController = SpecialPowers.Cc["@mozilla.org/satchel/form-fill-controller;1"].
|
||||
getService(SpecialPowers.Ci.nsIFormFillController);
|
||||
let usernameField = this.content.document.getElementById("form-basic-username");
|
||||
formFillController.markAsLoginManagerField(usernameField);
|
||||
});
|
||||
|
||||
info("Waiting to see if a popupshown occurs");
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
|
@ -9,9 +9,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript">
|
||||
const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerChild.jsm");
|
||||
const { LoginManagerChild } = LMCBackstagePass;
|
||||
|
||||
SimpleTest.requestFlakyTimeout("Testing that a message doesn't arrive");
|
||||
|
||||
let loadPromise = new Promise(resolve => {
|
||||
|
@ -149,13 +149,15 @@ add_task(async function test_3() {
|
||||
info("filled");
|
||||
|
||||
// check contents of iframe1 fields
|
||||
var u = SpecialPowers.wrap(iframe1).contentDocument.getElementById("userfield");
|
||||
var p = SpecialPowers.wrap(iframe1).contentDocument.getElementById("passfield");
|
||||
is(u.value, "user1", "checking expected user to have been filled in");
|
||||
is(p.value, "pass1", "checking expected pass to have been filled in");
|
||||
info("clearing fields to not cause a submission when the next document is loaded");
|
||||
u.value = "";
|
||||
p.value = "";
|
||||
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
var u = this.content.document.getElementById("userfield");
|
||||
var p = this.content.document.getElementById("passfield");
|
||||
Assert.equal(u.value, "user1", "checking expected user to have been filled in");
|
||||
Assert.equal(p.value, "pass1", "checking expected pass to have been filled in");
|
||||
u.value = "";
|
||||
p.value = "";
|
||||
});
|
||||
|
||||
ok(await isLoggedIn(), "should be logged in");
|
||||
logoutMasterPassword();
|
||||
@ -189,10 +191,12 @@ add_task(async function test_4() {
|
||||
await promptDone;
|
||||
|
||||
// check contents of iframe1 fields
|
||||
var u = SpecialPowers.wrap(iframe1).contentDocument.getElementById("userfield");
|
||||
var p = SpecialPowers.wrap(iframe1).contentDocument.getElementById("passfield");
|
||||
is(u.value, "", "checking expected empty user");
|
||||
is(p.value, "", "checking expected empty pass");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
var u = this.content.document.getElementById("userfield");
|
||||
var p = this.content.document.getElementById("passfield");
|
||||
Assert.equal(u.value, "", "checking expected empty user");
|
||||
Assert.equal(p.value, "", "checking expected empty pass");
|
||||
});
|
||||
|
||||
ok(!await isLoggedIn(), "should be logged out");
|
||||
|
||||
@ -227,10 +231,12 @@ add_task(async function test_4() {
|
||||
// is already waiting)
|
||||
|
||||
// check contents of iframe2 fields
|
||||
u = SpecialPowers.wrap(iframe2).contentDocument.getElementById("userfield");
|
||||
p = SpecialPowers.wrap(iframe2).contentDocument.getElementById("passfield");
|
||||
is(u.value, "", "checking expected empty user");
|
||||
is(p.value, "", "checking expected empty pass");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 1), [], function() {
|
||||
var u = this.content.document.getElementById("userfield");
|
||||
var p = this.content.document.getElementById("passfield");
|
||||
Assert.equal(u.value, "", "checking expected empty user");
|
||||
Assert.equal(p.value, "", "checking expected empty pass");
|
||||
});
|
||||
|
||||
// XXX check that there's 1 MP window open
|
||||
ok(!await isLoggedIn(), "should be logged out");
|
||||
@ -254,24 +260,28 @@ add_task(async function test_4() {
|
||||
ok(await isLoggedIn(), "should be logged in");
|
||||
|
||||
// check contents of iframe1 fields
|
||||
u = SpecialPowers.wrap(iframe1).contentDocument.getElementById("userfield");
|
||||
p = SpecialPowers.wrap(iframe1).contentDocument.getElementById("passfield");
|
||||
is(u.value, "user2", "checking expected user to have been filled in");
|
||||
is(p.value, "pass2", "checking expected pass to have been filled in");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
var u = this.content.document.getElementById("userfield");
|
||||
var p = this.content.document.getElementById("passfield");
|
||||
Assert.equal(u.value, "user2", "checking expected user to have been filled in");
|
||||
Assert.equal(p.value, "pass2", "checking expected pass to have been filled in");
|
||||
|
||||
info("clearing fields to not cause a submission when the next document is loaded");
|
||||
u.value = "";
|
||||
p.value = "";
|
||||
// clearing fields to not cause a submission when the next document is loaded
|
||||
u.value = "";
|
||||
p.value = "";
|
||||
});
|
||||
|
||||
// check contents of iframe2 fields
|
||||
u = SpecialPowers.wrap(iframe2).contentDocument.getElementById("userfield");
|
||||
p = SpecialPowers.wrap(iframe2).contentDocument.getElementById("passfield");
|
||||
is(u.value, "user1", "checking expected user to have been filled in");
|
||||
is(p.value, "pass1", "checking expected pass to have been filled in");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 1), [], function() {
|
||||
var u = this.content.document.getElementById("userfield");
|
||||
var p = this.content.document.getElementById("passfield");
|
||||
Assert.equal(u.value, "user1", "checking expected user to have been filled in");
|
||||
Assert.equal(p.value, "pass1", "checking expected pass to have been filled in");
|
||||
|
||||
info("clearing fields to not cause a submission when the next document is loaded");
|
||||
u.value = "";
|
||||
p.value = "";
|
||||
// clearing fields to not cause a submission when the next document is loaded
|
||||
u.value = "";
|
||||
p.value = "";
|
||||
});
|
||||
});
|
||||
|
||||
// XXX do a test5ABC with clicking cancel?
|
||||
|
@ -10,9 +10,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript">
|
||||
const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerChild.jsm");
|
||||
const { LoginManagerChild } = LMCBackstagePass;
|
||||
|
||||
let readyPromise = registerRunTests();
|
||||
|
||||
function add2logins() {
|
||||
@ -59,12 +56,13 @@ async function loadFormIntoIframe(origin, html) {
|
||||
});
|
||||
loginFrame.src = origin + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
|
||||
await loadedPromise;
|
||||
let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document;
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
frameDoc.documentElement.innerHTML = html;
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [html], function(contentHtml) {
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
this.content.document.documentElement.innerHTML = contentHtml;
|
||||
});
|
||||
|
||||
// Wait for the form to be processed before trying to submit.
|
||||
await promiseFormsProcessed();
|
||||
return frameDoc;
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
@ -146,16 +144,21 @@ function getSubmitMessage() {
|
||||
add_task(async function test_new_logins() {
|
||||
for (let tc of TESTCASES) {
|
||||
info("Starting testcase: " + JSON.stringify(tc));
|
||||
let frameDoc = await loadFormIntoIframe(DEFAULT_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
await loadFormIntoIframe(DEFAULT_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
<input type="text" name="uname" value="${tc.username}">
|
||||
<input type="password" name="pword" value="thepassword">
|
||||
<button type="submit" id="submitBtn">Submit</button>
|
||||
</form>`);
|
||||
is(frameDoc.querySelector("[name='uname']").value, tc.username, "Checking for filled username");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [tc], function(testcase) {
|
||||
let doc = this.content.document;
|
||||
Assert.equal(doc.querySelector("[name='uname']").value, testcase.username, "Checking for filled username");
|
||||
});
|
||||
|
||||
// Check data sent via PasswordManager:onFormSubmit
|
||||
let processedPromise = getSubmitMessage();
|
||||
frameDoc.getElementById("submitBtn").click();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
this.content.document.getElementById("submitBtn").click();
|
||||
});
|
||||
|
||||
let submittedResult = await processedPromise;
|
||||
info("Got submittedResult: " + JSON.stringify(submittedResult));
|
||||
@ -182,21 +185,26 @@ add_task(async function test_no_autofill_munged_username_matching_password() {
|
||||
let timesUsed = bulletLogin.timesUsed;
|
||||
let guid = bulletLogin.guid;
|
||||
|
||||
let frameDoc = await loadFormIntoIframe(ORG_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
await loadFormIntoIframe(ORG_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
<input type="text" name="uname" value="">
|
||||
<input type="password" name="pword" value="">
|
||||
<button type="submit" id="submitBtn">Submit</button>
|
||||
</form>`);
|
||||
is(frameDoc.querySelector("[name='uname']").value, "", "Check username didn't get autofilled");
|
||||
frameDoc.querySelector("[name='uname']").setUserInput("real••••user");
|
||||
frameDoc.querySelector("[name='pword']").setUserInput("pass1");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
let doc = this.content.document;
|
||||
Assert.equal(doc.querySelector("[name='uname']").value, "", "Check username didn't get autofilled");
|
||||
doc.querySelector("[name='uname']").setUserInput("real••••user");
|
||||
doc.querySelector("[name='pword']").setUserInput("pass1");
|
||||
});
|
||||
|
||||
// we shouldn't get the save password doorhanger...
|
||||
let popupShownPromise = promiseNoUnexpectedPopupShown();
|
||||
|
||||
// Check data sent via PasswordManager:onFormSubmit
|
||||
let processedPromise = getSubmitMessage();
|
||||
frameDoc.getElementById("submitBtn").click();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
this.content.document.getElementById("submitBtn").click();
|
||||
});
|
||||
|
||||
let submittedResult = await processedPromise;
|
||||
info("Got submittedResult: " + JSON.stringify(submittedResult));
|
||||
@ -227,20 +235,25 @@ add_task(async function test_autofill_munged_username_matching_password() {
|
||||
let timesUsed = bulletLogin.timesUsed;
|
||||
let guid = bulletLogin.guid;
|
||||
|
||||
let frameDoc = await loadFormIntoIframe(ORG_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
await loadFormIntoIframe(ORG_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
<input type="text" name="uname" value="">
|
||||
<input type="password" name="pword" value="">
|
||||
<button type="submit" id="submitBtn">Submit</button>
|
||||
</form>`);
|
||||
is(frameDoc.querySelector("[name='uname']").value, "real••••user", "Check username did get autofilled");
|
||||
frameDoc.querySelector("[name='pword']").setUserInput("pass1");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
let doc = this.content.document;
|
||||
Assert.equal(doc.querySelector("[name='uname']").value, "real••••user", "Check username did get autofilled");
|
||||
doc.querySelector("[name='pword']").setUserInput("pass1");
|
||||
});
|
||||
|
||||
// we shouldn't get the save/update password doorhanger as it didn't change
|
||||
let popupShownPromise = promiseNoUnexpectedPopupShown();
|
||||
|
||||
// Check data sent via PasswordManager:onFormSubmit
|
||||
let processedPromise = getSubmitMessage();
|
||||
frameDoc.getElementById("submitBtn").click();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
this.content.document.getElementById("submitBtn").click();
|
||||
});
|
||||
|
||||
let submittedResult = await processedPromise;
|
||||
info("Got submittedResult: " + JSON.stringify(submittedResult));
|
||||
|
@ -31,12 +31,14 @@ async function loadFormIntoIframe(origin, html) {
|
||||
});
|
||||
loginFrame.src = origin + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
|
||||
await loadedPromise;
|
||||
let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document;
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
frameDoc.documentElement.innerHTML = html;
|
||||
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [html], function(contentHtml) {
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
this.content.document.documentElement.innerHTML = contentHtml;
|
||||
});
|
||||
|
||||
// Wait for the form to be processed before trying to submit.
|
||||
await promiseFormsProcessed();
|
||||
return frameDoc;
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
@ -93,20 +95,25 @@ function getSubmitMessage() {
|
||||
add_task(async function test_password_lengths() {
|
||||
for (let tc of TESTCASES) {
|
||||
info("Starting testcase: " + tc.testName + ", " + JSON.stringify([tc.pword1, tc.pword2]));
|
||||
let frameDoc = await loadFormIntoIframe(DEFAULT_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
await loadFormIntoIframe(DEFAULT_ORIGIN, `<form id="form1" onsubmit="return false;">
|
||||
<input type="text" name="uname" value="myname">
|
||||
<input type="password" name="pword1" value="">
|
||||
<input type="password" name="pword2" value="">
|
||||
<button type="submit" id="submitBtn">Submit</button>
|
||||
</form>`);
|
||||
|
||||
is(frameDoc.querySelector("[name='uname']").value, "myname", "Checking for filled username");
|
||||
frameDoc.querySelector("[name='pword1']").setUserInput(tc.pword1);
|
||||
frameDoc.querySelector("[name='pword2']").setUserInput(tc.pword2);
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [tc], function(testcase) {
|
||||
let doc = this.content.document;
|
||||
Assert.equal(doc.querySelector("[name='uname']").value, "myname", "Checking for filled username");
|
||||
doc.querySelector("[name='pword1']").setUserInput(testcase.pword1);
|
||||
doc.querySelector("[name='pword2']").setUserInput(testcase.pword2);
|
||||
});
|
||||
|
||||
// Check data sent via PasswordManager:onFormSubmit
|
||||
let processedPromise = getSubmitMessage();
|
||||
frameDoc.getElementById("submitBtn").click();
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
this.content.document.getElementById("submitBtn").click();
|
||||
});
|
||||
|
||||
let submittedResult = await processedPromise;
|
||||
info("Got submittedResult: " + JSON.stringify(submittedResult));
|
||||
|
@ -216,25 +216,33 @@
|
||||
checkPromptState(promptState, expectedPromptState);
|
||||
}
|
||||
|
||||
let iframe1Doc = await iframe1DocPromise;
|
||||
let iframe2aDoc = await iframe2aDocPromise;
|
||||
let iframe2bDoc = await iframe2bDocPromise;
|
||||
await iframe1DocPromise;
|
||||
await iframe2aDocPromise;
|
||||
await iframe2bDocPromise;
|
||||
|
||||
let authok1 = iframe1Doc.getElementById("ok").textContent;
|
||||
let proxyok1 = iframe1Doc.getElementById("proxy").textContent;
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
let doc = this.content.document;
|
||||
let authok1 = doc.getElementById("ok").textContent;
|
||||
let proxyok1 = doc.getElementById("proxy").textContent;
|
||||
Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
});
|
||||
|
||||
let authok2a = iframe2aDoc.getElementById("ok").textContent;
|
||||
let proxyok2a = iframe2aDoc.getElementById("proxy").textContent;
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 1), [], function() {
|
||||
let doc = this.content.document;
|
||||
let authok2a = doc.getElementById("ok").textContent;
|
||||
let proxyok2a = doc.getElementById("proxy").textContent;
|
||||
Assert.equal(authok2a, "PASS", "WWW Authorization OK, frame2a");
|
||||
Assert.equal(proxyok2a, "PASS", "Proxy Authorization OK, frame2a");
|
||||
});
|
||||
|
||||
let authok2b = iframe2bDoc.getElementById("ok").textContent;
|
||||
let proxyok2b = iframe2bDoc.getElementById("proxy").textContent;
|
||||
|
||||
is(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
is(authok2a, "PASS", "WWW Authorization OK, frame2a");
|
||||
is(authok2b, "PASS", "WWW Authorization OK, frame2b");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(proxyok2a, "PASS", "Proxy Authorization OK, frame2a");
|
||||
is(proxyok2b, "PASS", "Proxy Authorization OK, frame2b");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 2), [], function() {
|
||||
let doc = this.content.document;
|
||||
let authok2b = doc.getElementById("ok").textContent;
|
||||
let proxyok2b = doc.getElementById("proxy").textContent;
|
||||
Assert.equal(authok2b, "PASS", "WWW Authorization OK, frame2b");
|
||||
Assert.equal(proxyok2b, "PASS", "Proxy Authorization OK, frame2b");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_threeSubframesWithSameProxyAndHTTPAuth() {
|
||||
@ -275,21 +283,26 @@
|
||||
});
|
||||
await handlePrompt(state, action);
|
||||
|
||||
let iframe1Doc = await iframe1DocPromise;
|
||||
|
||||
function checkIframe(frame) {
|
||||
let doc = SpecialPowers.wrap(frame).contentDocument;
|
||||
await iframe1DocPromise;
|
||||
|
||||
function checkIframe(frameid) {
|
||||
let doc = this.content.document;
|
||||
let authok = doc.getElementById("ok").textContent;
|
||||
let proxyok = doc.getElementById("proxy").textContent;
|
||||
|
||||
is(authok, "PASS", "WWW Authorization OK, " + frame.id);
|
||||
is(proxyok, "PASS", "Proxy Authorization OK, " + frame.id);
|
||||
Assert.equal(authok, "PASS", "WWW Authorization OK, " + frameid);
|
||||
Assert.equal(proxyok, "PASS", "Proxy Authorization OK, " + frameid);
|
||||
}
|
||||
|
||||
checkIframe(iframe1Doc.getElementById("iframe1"));
|
||||
checkIframe(iframe1Doc.getElementById("iframe2"));
|
||||
checkIframe(iframe1Doc.getElementById("iframe3"));
|
||||
let parentIFrameBC = SpecialPowers.wrap(window).getWindowGlobalChild().
|
||||
browsingContext.getChildren()[0];
|
||||
|
||||
let childIFrame = SpecialPowers.unwrap(parentIFrameBC.getChildren()[0]);
|
||||
await SpecialPowers.spawn(childIFrame, ["iframe1"], checkIframe);
|
||||
childIFrame = SpecialPowers.unwrap(parentIFrameBC.getChildren()[1]);
|
||||
await SpecialPowers.spawn(childIFrame, ["iframe2"], checkIframe);
|
||||
childIFrame = SpecialPowers.unwrap(parentIFrameBC.getChildren()[2]);
|
||||
await SpecialPowers.spawn(childIFrame, ["iframe3"], checkIframe);
|
||||
});
|
||||
|
||||
add_task(async function test_oneFrameWithUnauthenticatedProxy() {
|
||||
@ -372,13 +385,16 @@
|
||||
};
|
||||
await handlePrompt(state, action);
|
||||
|
||||
let iframe1Doc = await iframe1DocPromise;
|
||||
await iframe1DocPromise;
|
||||
|
||||
let authok1 = iframe1Doc.getElementById("ok").textContent;
|
||||
let proxyok1 = iframe1Doc.getElementById("proxy").textContent;
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
let doc = this.content.document;
|
||||
let authok1 = doc.getElementById("ok").textContent;
|
||||
let proxyok1 = doc.getElementById("proxy").textContent;
|
||||
|
||||
is(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
Assert.equal(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_hugePayloadCancelled() {
|
||||
@ -464,16 +480,19 @@
|
||||
};
|
||||
await handlePrompt(state, action);
|
||||
|
||||
let iframe1Doc = await iframe1DocPromise;
|
||||
await iframe1DocPromise;
|
||||
|
||||
let authok1 = iframe1Doc.getElementById("ok").textContent;
|
||||
let proxyok1 = iframe1Doc.getElementById("proxy").textContent;
|
||||
let footnote = iframe1Doc.getElementById("footnote").textContent;
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
let doc = this.content.document;
|
||||
let authok1 = doc.getElementById("ok").textContent;
|
||||
let proxyok1 = doc.getElementById("proxy").textContent;
|
||||
let footnote = doc.getElementById("footnote").textContent;
|
||||
|
||||
is(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
Assert.equal(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
Assert.equal(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_hugeProxySuccessWWWSuccess() {
|
||||
@ -504,15 +523,19 @@
|
||||
};
|
||||
await handlePrompt(state, action);
|
||||
|
||||
let iframe1Doc = await iframe1DocPromise;
|
||||
let authok1 = iframe1Doc.getElementById("ok").textContent;
|
||||
let proxyok1 = iframe1Doc.getElementById("proxy").textContent;
|
||||
let footnote = iframe1Doc.getElementById("footnote").textContent;
|
||||
await iframe1DocPromise;
|
||||
|
||||
is(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
let doc = this.content.document;
|
||||
let authok1 = doc.getElementById("ok").textContent;
|
||||
let proxyok1 = doc.getElementById("proxy").textContent;
|
||||
let footnote = doc.getElementById("footnote").textContent;
|
||||
|
||||
Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
Assert.equal(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_cancelSome() {
|
||||
@ -575,15 +598,19 @@
|
||||
};
|
||||
await handlePrompt(state, action);
|
||||
|
||||
let iframe1Doc = await iframe1DocPromise;
|
||||
let authok1 = iframe1Doc.getElementById("ok").textContent;
|
||||
let proxyok1 = iframe1Doc.getElementById("proxy").textContent;
|
||||
let footnote = iframe1Doc.getElementById("footnote").textContent;
|
||||
await iframe1DocPromise;
|
||||
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
|
||||
let doc = this.content.document;
|
||||
let authok1 = doc.getElementById("ok").textContent;
|
||||
let proxyok1 = doc.getElementById("proxy").textContent;
|
||||
let footnote = doc.getElementById("footnote").textContent;
|
||||
|
||||
Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
Assert.equal(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
});
|
||||
|
||||
is(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
@ -98,8 +98,8 @@ add_task(async function test_iframe() {
|
||||
iframe.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1";
|
||||
await promptDone;
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"},
|
||||
iframe.contentDocument);
|
||||
await checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"},
|
||||
iframe);
|
||||
|
||||
state = {
|
||||
msg: "http://mochi.test:8888 is requesting your username and password. The site says: “mochitest2”",
|
||||
@ -127,8 +127,8 @@ add_task(async function test_iframe() {
|
||||
iframe.src = "authenticate.sjs?user=mochiuser2&pass=mochipass2&realm=mochitest2";
|
||||
await promptDone;
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser2", pass: "mochipass2"},
|
||||
iframe.contentDocument);
|
||||
await checkEchoedAuthInfo({user: "mochiuser2", pass: "mochipass2"},
|
||||
iframe);
|
||||
|
||||
// Now make a load that requests the realm from test 1000. It was
|
||||
// already provided there, so auth will *not* be prompted for -- the
|
||||
@ -136,8 +136,8 @@ add_task(async function test_iframe() {
|
||||
iframeLoaded = onloadPromiseFor("iframe");
|
||||
iframe.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1";
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"},
|
||||
iframe.contentDocument);
|
||||
await checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"},
|
||||
iframe);
|
||||
|
||||
// Same realm we've already authenticated to, but with a different
|
||||
// expected password (to trigger an auth prompt, and change-password
|
||||
@ -167,8 +167,8 @@ add_task(async function test_iframe() {
|
||||
iframe.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1-new";
|
||||
await promptDone;
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1-new"},
|
||||
iframe.contentDocument);
|
||||
await checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1-new"},
|
||||
iframe);
|
||||
await promptShownPromise;
|
||||
|
||||
// Same as last test, but for a realm we haven't already authenticated
|
||||
@ -199,8 +199,8 @@ add_task(async function test_iframe() {
|
||||
iframe.src = "authenticate.sjs?user=mochiuser3&pass=mochipass3-new&realm=mochitest3";
|
||||
await promptDone;
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-new"},
|
||||
iframe.contentDocument);
|
||||
await checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-new"},
|
||||
iframe);
|
||||
await promptShownPromise;
|
||||
|
||||
// Housekeeping: Delete login4 to test the save prompt in the next test.
|
||||
@ -247,8 +247,8 @@ add_task(async function test_iframe() {
|
||||
iframe.src = "authenticate.sjs?user=mochiuser3&pass=mochipass3-old&realm=mochitest3";
|
||||
await promptDone;
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-old"},
|
||||
iframe.contentDocument);
|
||||
await checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-old"},
|
||||
iframe);
|
||||
await promptShownPromise;
|
||||
});
|
||||
|
||||
@ -281,8 +281,8 @@ add_task(async function test_schemeUpgrade() {
|
||||
"?user=httpUser&pass=httpPass&realm=schemeUpgrade";
|
||||
await promptDone;
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "httpUser", pass: "httpPass"},
|
||||
SpecialPowers.wrap(iframe).contentDocument);
|
||||
await checkEchoedAuthInfo({user: "httpUser", pass: "httpPass"},
|
||||
iframe);
|
||||
});
|
||||
|
||||
add_task(async function test_schemeDowngrade() {
|
||||
@ -345,8 +345,8 @@ add_task(async function test_schemeUpgrade_dedupe() {
|
||||
"?user=dedupeUser&pass=httpsPass&realm=schemeUpgradeDedupe";
|
||||
await promptDone;
|
||||
await iframeLoaded;
|
||||
checkEchoedAuthInfo({user: "dedupeUser", pass: "httpsPass"},
|
||||
SpecialPowers.wrap(iframe).contentDocument);
|
||||
await checkEchoedAuthInfo({user: "dedupeUser", pass: "httpsPass"},
|
||||
iframe);
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -20,14 +20,20 @@
|
||||
// Force parent to not look for tab-modal prompts, as they're not used for auth prompts.
|
||||
isTabModal = false;
|
||||
|
||||
runInParent(() => {
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
add_task(async function setup() {
|
||||
let loginAddedPromise = promiseStorageChanged(["addLogin"]);
|
||||
|
||||
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
||||
createInstance(Ci.nsILoginInfo);
|
||||
login.init("http://mochi.test:8888", null, "mochitest",
|
||||
"mochiuser1", "mochipass1", "", "");
|
||||
Services.logins.addLogin(login);
|
||||
runInParent(() => {
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
||||
createInstance(Ci.nsILoginInfo);
|
||||
login.init("http://mochi.test:8888", null, "mochitest",
|
||||
"mochiuser1", "mochipass1", "", "");
|
||||
Services.logins.addLogin(login);
|
||||
});
|
||||
|
||||
await loginAddedPromise;
|
||||
});
|
||||
|
||||
add_task(async function test_sandbox_xhr() {
|
||||
|
@ -25,6 +25,9 @@ function waitForFills(fillCount) {
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
// This test should run without any existing loaded recipes interfering.
|
||||
await resetRecipes();
|
||||
|
||||
if (document.readyState !== "complete") {
|
||||
await new Promise((resolve) => {
|
||||
document.onreadystatechange = () => {
|
||||
|
@ -14,6 +14,7 @@ add_task(async function test_doAutocompleteSearch_generated_noLogins() {
|
||||
Services.prefs.setBoolPref("signon.generation.enabled", true);
|
||||
|
||||
let LMP = new LoginManagerParent();
|
||||
LMP.useBrowsingContext(123);
|
||||
|
||||
ok(LMP.doAutocompleteSearch, "doAutocompleteSearch exists");
|
||||
|
||||
@ -26,7 +27,6 @@ add_task(async function test_doAutocompleteSearch_generated_noLogins() {
|
||||
fieldName: "new-password",
|
||||
canAutomaticallyPersist: false,
|
||||
},
|
||||
browsingContextId: 123,
|
||||
formOrigin: "https://example.com",
|
||||
actionOrigin: "https://mozilla.org",
|
||||
searchString: "",
|
||||
@ -36,21 +36,6 @@ add_task(async function test_doAutocompleteSearch_generated_noLogins() {
|
||||
isPasswordField: true,
|
||||
};
|
||||
|
||||
let sendMessageStub = sinon.stub();
|
||||
let fakeBrowser = {
|
||||
messageManager: {
|
||||
sendAsyncMessage: sendMessageStub,
|
||||
},
|
||||
ownerGlobal: {
|
||||
docShell: {
|
||||
// eslint-disable-next-line mozilla/use-chromeutils-generateqi
|
||||
QueryInterface() {
|
||||
return { usePrivateBrowsing: false };
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
sinon
|
||||
.stub(LoginManagerParent._browsingContextGlobal, "get")
|
||||
.withArgs(123)
|
||||
@ -64,72 +49,48 @@ add_task(async function test_doAutocompleteSearch_generated_noLogins() {
|
||||
};
|
||||
});
|
||||
|
||||
LMP.doAutocompleteSearch(arg1, fakeBrowser);
|
||||
ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
|
||||
let msg1 = sendMessageStub.firstCall.args[1];
|
||||
equal(msg1.requestId, arg1.requestId, "requestId matches");
|
||||
equal(msg1.logins.length, 0, "no logins");
|
||||
ok(msg1.generatedPassword, "has a generated password");
|
||||
equal(
|
||||
msg1.generatedPassword.length,
|
||||
LoginTestUtils.generation.LENGTH,
|
||||
"generated password length"
|
||||
);
|
||||
sendMessageStub.resetHistory();
|
||||
let result1 = LMP.doAutocompleteSearch(arg1);
|
||||
equal(result1.logins.length, 0, "no logins");
|
||||
ok(result1.generatedPassword, "has a generated password");
|
||||
equal(result1.generatedPassword.length, 15, "generated password length");
|
||||
|
||||
info("repeat the search and ensure the same password was used");
|
||||
LMP.doAutocompleteSearch(arg1, fakeBrowser);
|
||||
ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
|
||||
let msg2 = sendMessageStub.firstCall.args[1];
|
||||
equal(msg2.requestId, arg1.requestId, "requestId matches");
|
||||
equal(msg2.logins.length, 0, "no logins");
|
||||
let result2 = LMP.doAutocompleteSearch(arg1);
|
||||
equal(result2.logins.length, 0, "no logins");
|
||||
equal(
|
||||
msg2.generatedPassword,
|
||||
msg1.generatedPassword,
|
||||
result2.generatedPassword,
|
||||
result1.generatedPassword,
|
||||
"same generated password"
|
||||
);
|
||||
sendMessageStub.resetHistory();
|
||||
|
||||
info("Check cases where a password shouldn't be generated");
|
||||
|
||||
LMP.doAutocompleteSearch(
|
||||
{ ...arg1, ...{ isPasswordField: false } },
|
||||
fakeBrowser
|
||||
);
|
||||
ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
|
||||
let msg = sendMessageStub.firstCall.args[1];
|
||||
equal(msg.requestId, arg1.requestId, "requestId matches");
|
||||
let result3 = LMP.doAutocompleteSearch({
|
||||
...arg1,
|
||||
...{ isPasswordField: false },
|
||||
});
|
||||
equal(
|
||||
msg.generatedPassword,
|
||||
result3.generatedPassword,
|
||||
null,
|
||||
"no generated password when not a pw. field"
|
||||
);
|
||||
sendMessageStub.resetHistory();
|
||||
|
||||
let arg1_2 = { ...arg1 };
|
||||
arg1_2.autocompleteInfo.fieldName = "";
|
||||
LMP.doAutocompleteSearch(arg1_2, fakeBrowser);
|
||||
ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
|
||||
msg = sendMessageStub.firstCall.args[1];
|
||||
equal(msg.requestId, arg1.requestId, "requestId matches");
|
||||
let result4 = LMP.doAutocompleteSearch(arg1_2);
|
||||
equal(
|
||||
msg.generatedPassword,
|
||||
result4.generatedPassword,
|
||||
null,
|
||||
"no generated password when not autocomplete=new-password"
|
||||
);
|
||||
sendMessageStub.resetHistory();
|
||||
|
||||
LMP.doAutocompleteSearch(
|
||||
{ ...arg1, ...{ browsingContextId: 999 } },
|
||||
fakeBrowser
|
||||
);
|
||||
ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
|
||||
msg = sendMessageStub.firstCall.args[1];
|
||||
equal(msg.requestId, arg1.requestId, "requestId matches");
|
||||
let result5 = LMP.doAutocompleteSearch({
|
||||
...arg1,
|
||||
...{ browsingContextId: 999 },
|
||||
});
|
||||
equal(
|
||||
msg.generatedPassword,
|
||||
result5.generatedPassword,
|
||||
null,
|
||||
"no generated password with a missing browsingContextId"
|
||||
);
|
||||
sendMessageStub.resetHistory();
|
||||
});
|
||||
|
@ -15,6 +15,7 @@ add_task(async function test_getGeneratedPassword() {
|
||||
Services.prefs.setBoolPref("signon.generation.enabled", true);
|
||||
|
||||
let LMP = new LoginManagerParent();
|
||||
LMP.useBrowsingContext(99);
|
||||
|
||||
ok(LMP.getGeneratedPassword, "LMP.getGeneratedPassword exists");
|
||||
equal(
|
||||
@ -23,7 +24,7 @@ add_task(async function test_getGeneratedPassword() {
|
||||
"Empty cache to start"
|
||||
);
|
||||
|
||||
equal(LMP.getGeneratedPassword(99), null, "Null with no BrowsingContext");
|
||||
equal(LMP.getGeneratedPassword(), null, "Null with no BrowsingContext");
|
||||
|
||||
ok(
|
||||
LoginManagerParent._browsingContextGlobal,
|
||||
@ -51,7 +52,7 @@ add_task(async function test_getGeneratedPassword() {
|
||||
"Checking BrowsingContext.get(99) stub"
|
||||
);
|
||||
|
||||
let password1 = LMP.getGeneratedPassword(99);
|
||||
let password1 = LMP.getGeneratedPassword();
|
||||
notEqual(password1, null, "Check password was returned");
|
||||
equal(
|
||||
password1.length,
|
||||
@ -70,7 +71,7 @@ add_task(async function test_getGeneratedPassword() {
|
||||
password1,
|
||||
"Cache key and value"
|
||||
);
|
||||
let password2 = LMP.getGeneratedPassword(99);
|
||||
let password2 = LMP.getGeneratedPassword();
|
||||
equal(
|
||||
password1,
|
||||
password2,
|
||||
@ -91,7 +92,7 @@ add_task(async function test_getGeneratedPassword() {
|
||||
},
|
||||
};
|
||||
});
|
||||
let password3 = LMP.getGeneratedPassword(99);
|
||||
let password3 = LMP.getGeneratedPassword();
|
||||
notEqual(
|
||||
password2,
|
||||
password3,
|
||||
@ -106,19 +107,20 @@ add_task(async function test_getGeneratedPassword() {
|
||||
info("Now checks cases where null should be returned");
|
||||
|
||||
Services.prefs.setBoolPref("signon.rememberSignons", false);
|
||||
equal(LMP.getGeneratedPassword(99), null, "Prevented when pwmgr disabled");
|
||||
equal(LMP.getGeneratedPassword(), null, "Prevented when pwmgr disabled");
|
||||
Services.prefs.setBoolPref("signon.rememberSignons", true);
|
||||
|
||||
Services.prefs.setBoolPref("signon.generation.available", false);
|
||||
equal(LMP.getGeneratedPassword(99), null, "Prevented when unavailable");
|
||||
equal(LMP.getGeneratedPassword(), null, "Prevented when unavailable");
|
||||
Services.prefs.setBoolPref("signon.generation.available", true);
|
||||
|
||||
Services.prefs.setBoolPref("signon.generation.enabled", false);
|
||||
equal(LMP.getGeneratedPassword(99), null, "Prevented when disabled");
|
||||
equal(LMP.getGeneratedPassword(), null, "Prevented when disabled");
|
||||
Services.prefs.setBoolPref("signon.generation.enabled", true);
|
||||
|
||||
LMP.useBrowsingContext(123);
|
||||
equal(
|
||||
LMP.getGeneratedPassword(123),
|
||||
LMP.getGeneratedPassword(),
|
||||
null,
|
||||
"Prevented when browsingContext is missing"
|
||||
);
|
||||
|
@ -147,15 +147,13 @@ function checkEditTelemetryRecorded(expectedCount, msg) {
|
||||
}
|
||||
|
||||
function startTestConditions(contextId) {
|
||||
LMP.useBrowsingContext(contextId);
|
||||
|
||||
ok(
|
||||
LMP._onGeneratedPasswordFilledOrEdited,
|
||||
"LMP._onGeneratedPasswordFilledOrEdited exists"
|
||||
);
|
||||
equal(
|
||||
LMP.getGeneratedPassword(contextId),
|
||||
null,
|
||||
"Null with no BrowsingContext"
|
||||
);
|
||||
equal(LMP.getGeneratedPassword(), null, "Null with no BrowsingContext");
|
||||
equal(
|
||||
LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().size,
|
||||
0,
|
||||
@ -528,7 +526,7 @@ add_task(
|
||||
|
||||
add_task(
|
||||
async function test_onGeneratedPasswordFilledOrEdited_withSavedEmptyUsername() {
|
||||
startTestConditions();
|
||||
startTestConditions(99);
|
||||
let login0Props = Object.assign({}, loginTemplate, {
|
||||
username: "",
|
||||
password: "qweqweq",
|
||||
@ -714,7 +712,7 @@ add_task(
|
||||
|
||||
add_task(
|
||||
async function test_onGeneratedPasswordFilledOrEdited_withEmptyUsernameDifferentFormActionOrigin() {
|
||||
startTestConditions();
|
||||
startTestConditions(99);
|
||||
let login0Props = Object.assign({}, loginTemplate, {
|
||||
username: "",
|
||||
password: "qweqweq",
|
||||
@ -772,7 +770,7 @@ add_task(
|
||||
|
||||
add_task(
|
||||
async function test_onGeneratedPasswordFilledOrEdited_withSavedUsername() {
|
||||
startTestConditions();
|
||||
startTestConditions(99);
|
||||
let login0Props = Object.assign({}, loginTemplate, {
|
||||
username: "previoususer",
|
||||
password: "qweqweq",
|
||||
|
@ -4,6 +4,9 @@ module.exports = {
|
||||
"extends": [
|
||||
"plugin:mozilla/mochitest-test"
|
||||
],
|
||||
"globals": {
|
||||
"Assert": true,
|
||||
},
|
||||
"rules": {
|
||||
// ownerGlobal doesn't exist in content privileged windows.
|
||||
"mozilla/use-ownerGlobal": "off",
|
||||
|
@ -187,15 +187,23 @@ function checkPromptState(promptState, expectedState) {
|
||||
}
|
||||
}
|
||||
|
||||
function checkEchoedAuthInfo(expectedState, doc) {
|
||||
// The server echos back the HTTP auth info it received.
|
||||
let username = doc.getElementById("user").textContent;
|
||||
let password = doc.getElementById("pass").textContent;
|
||||
let authok = doc.getElementById("ok").textContent;
|
||||
function checkEchoedAuthInfo(expectedState, browsingContext) {
|
||||
return SpecialPowers.spawn(
|
||||
browsingContext,
|
||||
[expectedState.user, expectedState.pass],
|
||||
(expectedUser, expectedPass) => {
|
||||
let doc = this.content.document;
|
||||
|
||||
is(authok, "PASS", "Checking for successful authentication");
|
||||
is(username, expectedState.user, "Checking for echoed username");
|
||||
is(password, expectedState.pass, "Checking for echoed password");
|
||||
// The server echos back the HTTP auth info it received.
|
||||
let username = doc.getElementById("user").textContent;
|
||||
let password = doc.getElementById("pass").textContent;
|
||||
let authok = doc.getElementById("ok").textContent;
|
||||
|
||||
Assert.equal(authok, "PASS", "Checking for successful authentication");
|
||||
Assert.equal(username, expectedUser, "Checking for echoed username");
|
||||
Assert.equal(password, expectedPass, "Checking for echoed password");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,8 +145,8 @@ add_task(async function runTestAuth() {
|
||||
iframe_prompt.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1";
|
||||
await promptDone;
|
||||
await iframe3Loaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"},
|
||||
iframe_prompt.contentDocument);
|
||||
await checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"},
|
||||
iframe_prompt);
|
||||
|
||||
// Cross-origin subresourse test.
|
||||
|
||||
@ -182,8 +182,8 @@ add_task(async function runTestAuth() {
|
||||
iframe_prompt.src = "http://example.com/tests/toolkit/components/prompts/test/authenticate.sjs?user=mochiuser2&pass=mochipass2&realm=mochitest";
|
||||
await promptDone;
|
||||
await iframe3Loaded;
|
||||
checkEchoedAuthInfo({user: "mochiuser2", pass: "mochipass2"},
|
||||
SpecialPowers.wrap(iframe_prompt.contentWindow).document);
|
||||
await checkEchoedAuthInfo({user: "mochiuser2", pass: "mochipass2"},
|
||||
iframe_prompt);
|
||||
});
|
||||
|
||||
function dispatchMouseEvent(target, type) {
|
||||
|
@ -275,6 +275,33 @@ let ACTORS = {
|
||||
allFrames: true,
|
||||
},
|
||||
|
||||
LoginManager: {
|
||||
parent: {
|
||||
moduleURI: "resource://gre/modules/LoginManagerParent.jsm",
|
||||
messages: [
|
||||
"PasswordManager:findLogins",
|
||||
"PasswordManager:onFormSubmit",
|
||||
"PasswordManager:onGeneratedPasswordFilledOrEdited",
|
||||
"PasswordManager:insecureLoginFormPresent",
|
||||
"PasswordManager:autoCompleteLogins",
|
||||
"PasswordManager:removeLogin",
|
||||
"PasswordManager:OpenPreferences",
|
||||
"PasswordManager:formProcessed",
|
||||
],
|
||||
},
|
||||
child: {
|
||||
moduleURI: "resource://gre/modules/LoginManagerChild.jsm",
|
||||
messages: [
|
||||
"PasswordManager:fillForm",
|
||||
"PasswordManager:fillGeneratedPassword",
|
||||
"FormAutoComplete:PopupOpened",
|
||||
"FormAutoComplete:PopupClosed",
|
||||
],
|
||||
},
|
||||
|
||||
allFrames: true,
|
||||
},
|
||||
|
||||
Select: {
|
||||
parent: {
|
||||
moduleURI: "resource://gre/actors/SelectParent.jsm",
|
||||
|
Loading…
Reference in New Issue
Block a user