зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1149975 - Part 1 of 2 - Handle visibility of the login fill doorhanger anchor. r=MattN
--HG-- extra : rebase_source : fdc290d5179bf79af1198bf62b5aac98e30aa894
This commit is contained in:
Родитель
f66879ad04
Коммит
80dddf5864
|
@ -773,8 +773,8 @@ window[chromehidden~="toolbar"] toolbar:not(#nav-bar):not(#TabsToolbar):not(#pri
|
|||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
|
||||
}
|
||||
|
||||
#password-fill-notification {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#password-fill-notification");
|
||||
#login-fill-notification {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#login-fill-notification");
|
||||
}
|
||||
|
||||
.login-fill-item {
|
||||
|
|
|
@ -783,6 +783,7 @@
|
|||
<image id="push-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="login-fill-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
|
|
|
@ -49,9 +49,15 @@ addMessageListener("ContextMenu:DoCustomCommand", function(message) {
|
|||
PageMenuChild.executeMenu(message.data);
|
||||
});
|
||||
|
||||
addMessageListener("RemoteLogins:fillForm", function(message) {
|
||||
LoginManagerContent.receiveMessage(message, content);
|
||||
});
|
||||
addEventListener("DOMFormHasPassword", function(event) {
|
||||
LoginManagerContent.onDOMFormHasPassword(event, content);
|
||||
InsecurePasswordUtils.checkForInsecurePasswords(event.target);
|
||||
LoginManagerContent.onFormPassword(event);
|
||||
});
|
||||
addEventListener("pageshow", function(event) {
|
||||
LoginManagerContent.onPageShow(event, content);
|
||||
});
|
||||
addEventListener("DOMAutoComplete", function(event) {
|
||||
LoginManagerContent.onUsernameInput(event);
|
||||
|
|
|
@ -2781,7 +2781,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
which is empty because the actual panel is not implemented inside an XBL
|
||||
binding, but made of elements added to the notification panel. This
|
||||
allows accessing the full structure while the panel is hidden. -->
|
||||
<binding id="password-fill-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
|
||||
<binding id="login-fill-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
|
||||
<content>
|
||||
<children/>
|
||||
</content>
|
||||
|
|
|
@ -8,11 +8,25 @@
|
|||
max-height: 20em;
|
||||
}
|
||||
|
||||
.login-fill-item[disabled] {
|
||||
color: #888;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.login-fill-item[disabled][selected] {
|
||||
background-color: #eef;
|
||||
}
|
||||
|
||||
.login-hostname {
|
||||
margin: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-fill-item.different-hostname > .login-hostname {
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.login-username {
|
||||
margin: 4px;
|
||||
color: #888;
|
||||
|
|
|
@ -140,6 +140,12 @@
|
|||
list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
|
||||
}
|
||||
|
||||
#login-fill-notification-icon {
|
||||
/* Temporary icon until the capture and fill doorhangers are unified. */
|
||||
list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.webapps-notification-icon,
|
||||
#webapps-notification-icon {
|
||||
list-style-image: url(chrome://global/skin/icons/webapps-16.png);
|
||||
|
@ -311,6 +317,7 @@
|
|||
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
|
||||
}
|
||||
|
||||
#login-fill-notification-icon,
|
||||
#password-notification-icon {
|
||||
list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16@2x.png);
|
||||
}
|
||||
|
|
|
@ -4223,7 +4223,8 @@ Tab.prototype = {
|
|||
}
|
||||
|
||||
case "DOMFormHasPassword": {
|
||||
LoginManagerContent.onFormPassword(aEvent);
|
||||
LoginManagerContent.onDOMFormHasPassword(aEvent,
|
||||
this.browser.contentWindow);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -4365,7 +4366,9 @@ Tab.prototype = {
|
|||
}
|
||||
|
||||
case "pageshow": {
|
||||
// only send pageshow for the top-level document
|
||||
LoginManagerContent.onPageShow(aEvent, this.browser.contentWindow);
|
||||
|
||||
// The rest of this only handles pageshow for the top-level document.
|
||||
if (aEvent.originalTarget.defaultView != this.browser.contentWindow)
|
||||
return;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AboutReader", "resource://gre/modules/AboutReader.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm");
|
||||
|
||||
let dump = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "Content");
|
||||
|
||||
|
@ -87,3 +88,7 @@ let AboutReaderListener = {
|
|||
}
|
||||
};
|
||||
AboutReaderListener.init();
|
||||
|
||||
addMessageListener("RemoteLogins:fillForm", function(message) {
|
||||
LoginManagerContent.receiveMessage(message, content);
|
||||
});
|
||||
|
|
|
@ -3812,6 +3812,7 @@ pref("signon.rememberSignons", true);
|
|||
pref("signon.autofillForms", true);
|
||||
pref("signon.autologin.proxy", false);
|
||||
pref("signon.storeWhenAutocompleteOff", true);
|
||||
pref("signon.ui.experimental", false);
|
||||
pref("signon.debug", false);
|
||||
|
||||
// Satchel (Form Manager) prefs
|
||||
|
|
|
@ -11,6 +11,7 @@ this.EXPORTED_SYMBOLS = [
|
|||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/LoginManagerParent.jsm");
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
|
@ -27,10 +28,8 @@ this.LoginDoorhangers.FillDoorhanger = function (properties) {
|
|||
this.onListDblClick = this.onListDblClick.bind(this);
|
||||
this.onListKeyPress = this.onListKeyPress.bind(this);
|
||||
|
||||
this.filterString = properties.filterString;
|
||||
|
||||
if (properties.browser) {
|
||||
this.browser = properties.browser;
|
||||
for (let name of Object.getOwnPropertyNames(properties)) {
|
||||
this[name] = properties[name];
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -48,20 +47,25 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
* web page is moved to a different chrome window by the swapDocShells method.
|
||||
*/
|
||||
set browser(browser) {
|
||||
const MAX_DATE_VALUE = new Date(8640000000000000);
|
||||
|
||||
this._browser = browser;
|
||||
|
||||
let doorhanger = this;
|
||||
let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
|
||||
let notification = PopupNotifications.show(
|
||||
browser,
|
||||
"password-fill",
|
||||
"login-fill",
|
||||
"",
|
||||
"password-notification-icon",
|
||||
"login-fill-notification-icon",
|
||||
null,
|
||||
null,
|
||||
{
|
||||
dismissed: true,
|
||||
persistWhileVisible: true,
|
||||
// This will make the anchor persist forever even if the popup is not
|
||||
// visible. We'll remove the notification manually when the page
|
||||
// changes, after we had time to check its final state asynchronously.
|
||||
timeout: MAX_DATE_VALUE,
|
||||
eventCallback: function (topic, otherBrowser) {
|
||||
switch (topic) {
|
||||
case "shown":
|
||||
|
@ -69,6 +73,8 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
// be called after the "show" method returns, so the reference to
|
||||
// "this.notification" will be available at this point.
|
||||
doorhanger.bound = true;
|
||||
doorhanger.promiseHidden =
|
||||
new Promise(resolve => doorhanger.onUnbind = resolve);
|
||||
doorhanger.bind();
|
||||
break;
|
||||
|
||||
|
@ -76,11 +82,12 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
case "removed":
|
||||
if (doorhanger.bound) {
|
||||
doorhanger.unbind();
|
||||
doorhanger.onUnbind();
|
||||
}
|
||||
break;
|
||||
|
||||
case "swapping":
|
||||
this._browser = otherBrowser;
|
||||
doorhanger._browser = otherBrowser;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -116,6 +123,11 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise resolved as soon as the notification is hidden.
|
||||
*/
|
||||
promiseHidden: Promise.resolve(),
|
||||
|
||||
/**
|
||||
* Removes the doorhanger from the browser.
|
||||
*/
|
||||
|
@ -159,6 +171,18 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
this.chomeDocument.getElementById("mainPopupSet").appendChild(this.element);
|
||||
},
|
||||
|
||||
/**
|
||||
* Origin for which the manual fill UI should be displayed, for example
|
||||
* "http://www.example.com".
|
||||
*/
|
||||
loginFormOrigin: "",
|
||||
|
||||
/**
|
||||
* When no login form is present on the page, we may still display a list of
|
||||
* logins, but we cannot offer manual filling.
|
||||
*/
|
||||
loginFormPresent: false,
|
||||
|
||||
/**
|
||||
* User-editable string used to filter the list of all logins.
|
||||
*/
|
||||
|
@ -192,6 +216,12 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
item.classList.add("login-fill-item");
|
||||
item.setAttribute("hostname", hostname);
|
||||
item.setAttribute("username", username);
|
||||
if (hostname != this.loginFormOrigin) {
|
||||
item.classList.add("different-hostname");
|
||||
}
|
||||
if (!this.loginFormPresent) {
|
||||
item.setAttribute("disabled", "true");
|
||||
}
|
||||
this.list.appendChild(item);
|
||||
}
|
||||
},
|
||||
|
@ -222,6 +252,23 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
this.fillLogin();
|
||||
},
|
||||
fillLogin() {
|
||||
if (this.list.selectedItem.hasAttribute("disabled")) {
|
||||
return;
|
||||
}
|
||||
let formLogins = Services.logins.findLogins({}, "", "", null);
|
||||
let login = formLogins.find(login => {
|
||||
return login.hostname == this.list.selectedItem.getAttribute("hostname") &&
|
||||
login.username == this.list.selectedItem.getAttribute("username");
|
||||
});
|
||||
if (login) {
|
||||
LoginManagerParent.fillForm({
|
||||
browser: this.browser,
|
||||
loginFormOrigin: this.loginFormOrigin,
|
||||
login,
|
||||
}).catch(Cu.reportError);
|
||||
} else {
|
||||
Cu.reportError("The selected login has been removed in the meantime.");
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
};
|
||||
|
@ -238,7 +285,7 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
|
|||
*/
|
||||
this.LoginDoorhangers.FillDoorhanger.find = function ({ browser }) {
|
||||
let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
|
||||
let notification = PopupNotifications.getNotification("password-fill",
|
||||
let notification = PopupNotifications.getNotification("login-fill",
|
||||
browser);
|
||||
return notification && notification.doorhanger;
|
||||
};
|
||||
|
|
|
@ -164,7 +164,7 @@ var LoginManagerContent = {
|
|||
return deferred.promise;
|
||||
},
|
||||
|
||||
receiveMessage: function (msg) {
|
||||
receiveMessage: function (msg, window) {
|
||||
// Convert an array of logins in simple JS-object form to an array of
|
||||
// nsILoginInfo objects.
|
||||
function jsLoginsToXPCOM(logins) {
|
||||
|
@ -179,6 +179,16 @@ var LoginManagerContent = {
|
|||
});
|
||||
}
|
||||
|
||||
if (msg.name == "RemoteLogins:fillForm") {
|
||||
this.fillForm({
|
||||
topDocument: window.document,
|
||||
loginFormOrigin: msg.data.loginFormOrigin,
|
||||
loginsFound: jsLoginsToXPCOM(msg.data.logins),
|
||||
recipes: msg.data.recipes,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let request = this._takeRequest(msg);
|
||||
switch (msg.name) {
|
||||
case "RemoteLogins:loginsFound": {
|
||||
|
@ -253,35 +263,145 @@ var LoginManagerContent = {
|
|||
messageData);
|
||||
},
|
||||
|
||||
/*
|
||||
* onFormPassword
|
||||
*
|
||||
* Called when an <input type="password"> element is added to the page
|
||||
*/
|
||||
onFormPassword: function (event) {
|
||||
if (!event.isTrusted)
|
||||
onDOMFormHasPassword(event, window) {
|
||||
if (!event.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
let form = event.target;
|
||||
|
||||
let doc = form.ownerDocument;
|
||||
let win = doc.defaultView;
|
||||
let messageManager = messageManagerFromWindow(win);
|
||||
// Always record the most recently added form with a password field.
|
||||
this.stateForDocument(form.ownerDocument).loginForm = form;
|
||||
|
||||
this._updateLoginFormPresence(window);
|
||||
|
||||
let messageManager = messageManagerFromWindow(window);
|
||||
messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
|
||||
|
||||
if (!gEnabled)
|
||||
if (!gEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
log("onFormPassword for", form.ownerDocument.documentURI);
|
||||
log("onDOMFormHasPassword for", form.ownerDocument.documentURI);
|
||||
this._getLoginDataFromParent(form, { showMasterPassword: true })
|
||||
.then(this.loginsFound.bind(this))
|
||||
.then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
onPageShow(event, window) {
|
||||
this._updateLoginFormPresence(window);
|
||||
},
|
||||
|
||||
/**
|
||||
* Maps all DOM content documents in this content process, including those in
|
||||
* frames, to the current state used by the Login Manager.
|
||||
*/
|
||||
loginFormStateByDocument: new WeakMap(),
|
||||
|
||||
/**
|
||||
* Retrieves a reference to the state object associated with the given
|
||||
* document. This is initialized to an empty object.
|
||||
*/
|
||||
stateForDocument(document) {
|
||||
let loginFormState = this.loginFormStateByDocument.get(document);
|
||||
if (!loginFormState) {
|
||||
loginFormState = {};
|
||||
this.loginFormStateByDocument.set(document, loginFormState);
|
||||
}
|
||||
return loginFormState;
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute whether there is a login form on any frame of the current page, and
|
||||
* notify the parent process. This is one of the factors used to control the
|
||||
* visibility of the password fill doorhanger anchor.
|
||||
*/
|
||||
_updateLoginFormPresence(topWindow) {
|
||||
// For the login form presence notification, we currently support only one
|
||||
// origin for each browser, so the form origin will always match the origin
|
||||
// of the top level document.
|
||||
let loginFormOrigin =
|
||||
LoginUtils._getPasswordOrigin(topWindow.document.documentURI);
|
||||
|
||||
// Returns the first known loginForm present in this window or in any
|
||||
// same-origin subframes. Returns null if no loginForm is currently present.
|
||||
let getFirstLoginForm = thisWindow => {
|
||||
let loginForm = this.stateForDocument(thisWindow.document).loginForm;
|
||||
if (loginForm) {
|
||||
return loginForm;
|
||||
}
|
||||
for (let i = 0; i < thisWindow.frames.length; i++) {
|
||||
let frame = thisWindow.frames[i];
|
||||
if (LoginUtils._getPasswordOrigin(frame.document.documentURI) !=
|
||||
loginFormOrigin) {
|
||||
continue;
|
||||
}
|
||||
let loginForm = getFirstLoginForm(frame);
|
||||
if (loginForm) {
|
||||
return loginForm;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Store the actual form to use on the state for the top-level document.
|
||||
let topState = this.stateForDocument(topWindow.document);
|
||||
topState.loginFormForFill = getFirstLoginForm(topWindow);
|
||||
|
||||
// Determine whether to show the anchor icon for the current tab.
|
||||
let messageManager = messageManagerFromWindow(topWindow);
|
||||
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
|
||||
loginFormOrigin,
|
||||
loginFormPresent: !!topState.loginFormForFill,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform a password fill upon user request coming from the parent process.
|
||||
* The fill will be in the form previously identified during page navigation.
|
||||
*
|
||||
* @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.
|
||||
* loginsFound:
|
||||
* Array containing the login to fill. While other messages may
|
||||
* have more logins, for this use case this is expected to have
|
||||
* exactly one element. The origin of the login may be different
|
||||
* from the origin of the form used for the fill.
|
||||
* recipes:
|
||||
* Fill recipes transmitted together with the original message.
|
||||
* }
|
||||
*/
|
||||
fillForm({ topDocument, loginFormOrigin, loginsFound, recipes }) {
|
||||
let topState = this.stateForDocument(topDocument);
|
||||
if (!topState.loginFormForFill) {
|
||||
log("fillForm: There is no login form anymore. The form may have been",
|
||||
"removed or the document may have changed.");
|
||||
return;
|
||||
}
|
||||
if (LoginUtils._getPasswordOrigin(topDocument.documentURI) !=
|
||||
loginFormOrigin) {
|
||||
log("fillForm: The requested origin doesn't match the one form the",
|
||||
"document. This may mean we navigated to a document from a different",
|
||||
"site before we had a chance to indicate this change in the user",
|
||||
"interface.");
|
||||
return;
|
||||
}
|
||||
this._fillForm(topState.loginFormForFill, true, true, true, true,
|
||||
loginsFound, recipes);
|
||||
},
|
||||
|
||||
loginsFound: function({ form, loginsFound, recipes }) {
|
||||
let doc = form.ownerDocument;
|
||||
let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
|
||||
|
||||
this._fillForm(form, autofillForm, false, false, loginsFound, recipes);
|
||||
this._fillForm(form, autofillForm, false, false, false, loginsFound, recipes);
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -324,7 +444,7 @@ var LoginManagerContent = {
|
|||
if (usernameField == acInputField && passwordField) {
|
||||
this._getLoginDataFromParent(acForm, { showMasterPassword: false })
|
||||
.then(({ form, loginsFound, recipes }) => {
|
||||
this._fillForm(form, true, true, true, loginsFound, recipes);
|
||||
this._fillForm(form, true, false, true, true, loginsFound, recipes);
|
||||
})
|
||||
.then(null, Cu.reportError);
|
||||
} else {
|
||||
|
@ -626,6 +746,8 @@ var LoginManagerContent = {
|
|||
*
|
||||
* @param {HTMLFormElement} form
|
||||
* @param {bool} autofillForm denotes if we should fill the form in automatically
|
||||
* @param {bool} clobberUsername controls if an existing username can be
|
||||
* overwritten
|
||||
* @param {bool} clobberPassword controls if an existing password value can be
|
||||
* overwritten
|
||||
* @param {bool} userTriggered is an indication of whether this filling was triggered by
|
||||
|
@ -633,7 +755,7 @@ var LoginManagerContent = {
|
|||
* @param {nsILoginInfo[]} foundLogins is an array of nsILoginInfo that could be used for the form
|
||||
* @param {Set} recipes that could be used to affect how the form is filled
|
||||
*/
|
||||
_fillForm : function (form, autofillForm, clobberPassword,
|
||||
_fillForm : function (form, autofillForm, clobberUsername, clobberPassword,
|
||||
userTriggered, foundLogins, recipes) {
|
||||
let ignoreAutocomplete = true;
|
||||
const AUTOFILL_RESULT = {
|
||||
|
@ -737,7 +859,9 @@ var LoginManagerContent = {
|
|||
|
||||
// Select a login to use for filling in the form.
|
||||
var selectedLogin;
|
||||
if (usernameField && (usernameField.value || usernameField.disabled || usernameField.readOnly)) {
|
||||
if (!clobberUsername && usernameField && (usernameField.value ||
|
||||
usernameField.disabled ||
|
||||
usernameField.readOnly)) {
|
||||
// If username was specified in the field, it's disabled or it's readOnly, only fill in the
|
||||
// password if we find a matching login.
|
||||
var username = usernameField.value.toLowerCase();
|
||||
|
|
|
@ -15,6 +15,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "UserAutoCompleteResult",
|
|||
"resource://gre/modules/LoginManagerContent.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AutoCompleteE10S",
|
||||
"resource://gre/modules/AutoCompleteE10S.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
|
||||
"resource://gre/modules/DeferredTask.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoginDoorhangers",
|
||||
"resource://gre/modules/LoginDoorhangers.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "LoginManagerParent", "PasswordsMetricsProvider" ];
|
||||
|
||||
|
@ -168,6 +172,7 @@ var LoginManagerParent = {
|
|||
mm.addMessageListener("RemoteLogins:findLogins", this);
|
||||
mm.addMessageListener("RemoteLogins:onFormSubmit", this);
|
||||
mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
|
||||
mm.addMessageListener("RemoteLogins:updateLoginFormPresence", this);
|
||||
mm.addMessageListener("LoginStats:LoginEncountered", this);
|
||||
mm.addMessageListener("LoginStats:LoginFillSuccessful", this);
|
||||
Services.obs.addObserver(this, "LoginStats:NewSavedPassword", false);
|
||||
|
@ -216,6 +221,11 @@ var LoginManagerParent = {
|
|||
break;
|
||||
}
|
||||
|
||||
case "RemoteLogins:updateLoginFormPresence": {
|
||||
this.updateLoginFormPresence(msg.target, data);
|
||||
break;
|
||||
}
|
||||
|
||||
case "RemoteLogins:autoCompleteLogins": {
|
||||
this.doAutocompleteSearch(data, msg.target);
|
||||
break;
|
||||
|
@ -241,6 +251,33 @@ var LoginManagerParent = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger a login form fill and send relevant data (e.g. logins and recipes)
|
||||
* to the child process (LoginManagerContent).
|
||||
*/
|
||||
fillForm: Task.async(function* ({ browser, loginFormOrigin, login }) {
|
||||
let recipes = [];
|
||||
if (loginFormOrigin) {
|
||||
let formHost;
|
||||
try {
|
||||
formHost = (new URL(loginFormOrigin)).host;
|
||||
let recipeManager = yield this.recipeParentPromise;
|
||||
recipes = recipeManager.getRecipesForHost(formHost);
|
||||
} catch (ex) {
|
||||
// Some schemes e.g. chrome aren't supported by URL
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
|
||||
// doesn't support structured cloning.
|
||||
let jsLogins = JSON.parse(JSON.stringify([login]));
|
||||
browser.messageManager.sendAsyncMessage("RemoteLogins:fillForm", {
|
||||
loginFormOrigin,
|
||||
logins: jsLogins,
|
||||
recipes,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
|
||||
*/
|
||||
|
@ -281,14 +318,14 @@ var LoginManagerParent = {
|
|||
// 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 onFormPassword for", formOrigin);
|
||||
log("deferring sendLoginDataToChild for", formOrigin);
|
||||
let self = this;
|
||||
let observer = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
log("Got deferred onFormPassword notification:", topic);
|
||||
log("Got deferred sendLoginDataToChild notification:", topic);
|
||||
// Only run observer once.
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
|
||||
|
@ -507,5 +544,101 @@ var LoginManagerParent = {
|
|||
// Prompt user to save login (via dialog or notification bar)
|
||||
prompter = getPrompter();
|
||||
prompter.promptToSavePassword(formLogin);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Maps all the <browser> elements for tabs in the parent process to the
|
||||
* current state used to display tab-specific UI.
|
||||
*
|
||||
* This mapping is not updated in case a web page is moved to a different
|
||||
* chrome window by the swapDocShells method. In this case, it is possible
|
||||
* that a UI update just requested for the login fill doorhanger and then
|
||||
* delayed by a few hundred milliseconds will be lost. Later requests would
|
||||
* use the new browser reference instead.
|
||||
*
|
||||
* Given that the case above is rare, and it would not cause any origin
|
||||
* mismatch at the time of filling because the origin is checked later in the
|
||||
* content process, this case is left unhandled.
|
||||
*/
|
||||
loginFormStateByBrowser: new WeakMap(),
|
||||
|
||||
/**
|
||||
* Retrieves a reference to the state object associated with the given
|
||||
* browser. This is initialized to an empty object.
|
||||
*/
|
||||
stateForBrowser(browser) {
|
||||
let loginFormState = this.loginFormStateByBrowser.get(browser);
|
||||
if (!loginFormState) {
|
||||
loginFormState = {};
|
||||
this.loginFormStateByBrowser.set(browser, loginFormState);
|
||||
}
|
||||
return loginFormState;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called to indicate whether a login form on the currently loaded page is
|
||||
* present or not. This is one of the factors used to control the visibility
|
||||
* of the password fill doorhanger.
|
||||
*/
|
||||
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
|
||||
const ANCHOR_DELAY_MS = 200;
|
||||
|
||||
let state = this.stateForBrowser(browser);
|
||||
|
||||
// Update the data to use to the latest known values. Since messages are
|
||||
// processed in order, this will always be the latest version to use.
|
||||
state.loginFormOrigin = loginFormOrigin;
|
||||
state.loginFormPresent = loginFormPresent;
|
||||
|
||||
// Apply the data to the currently displayed icon later.
|
||||
if (!state.anchorDeferredTask) {
|
||||
state.anchorDeferredTask = new DeferredTask(
|
||||
() => this.updateLoginAnchor(browser),
|
||||
ANCHOR_DELAY_MS
|
||||
);
|
||||
}
|
||||
state.anchorDeferredTask.arm();
|
||||
},
|
||||
updateLoginAnchor: Task.async(function* (browser) {
|
||||
// Copy the state to use for this execution of the task. These will not
|
||||
// change during this execution of the asynchronous function, but in case a
|
||||
// change happens in the state, the function will be retriggered.
|
||||
let { loginFormOrigin, loginFormPresent } = this.stateForBrowser(browser);
|
||||
|
||||
yield Services.logins.initializationPromise;
|
||||
|
||||
// Check if there are form logins for the site, ignoring formSubmitURL.
|
||||
let hasLogins = loginFormOrigin &&
|
||||
Services.logins.countLogins(loginFormOrigin, "", null) > 0;
|
||||
|
||||
// Once this preference is removed, this version of the fill doorhanger
|
||||
// should be enabled for Desktop only, and not for Android or B2G.
|
||||
if (!Services.prefs.getBoolPref("signon.ui.experimental")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let showLoginAnchor = loginFormPresent || hasLogins;
|
||||
|
||||
let fillDoorhanger = LoginDoorhangers.FillDoorhanger.find({ browser });
|
||||
if (fillDoorhanger) {
|
||||
if (!showLoginAnchor) {
|
||||
fillDoorhanger.remove();
|
||||
return;
|
||||
}
|
||||
// We should only update the state of the doorhanger while it is hidden.
|
||||
yield fillDoorhanger.promiseHidden;
|
||||
fillDoorhanger.loginFormPresent = loginFormPresent;
|
||||
fillDoorhanger.loginFormOrigin = loginFormOrigin;
|
||||
fillDoorhanger.filterString = loginFormOrigin;
|
||||
return;
|
||||
}
|
||||
if (showLoginAnchor) {
|
||||
fillDoorhanger = new LoginDoorhangers.FillDoorhanger({
|
||||
browser,
|
||||
loginFormPresent,
|
||||
loginFormOrigin,
|
||||
filterString: loginFormOrigin,
|
||||
});
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -120,8 +120,6 @@ LoginManager.prototype = {
|
|||
// Form submit observer checks forms for new logins and pw changes.
|
||||
Services.obs.addObserver(this._observer, "xpcom-shutdown", false);
|
||||
|
||||
// TODO: Make this class useful in the child process (in addition to
|
||||
// autoCompleteSearchAsync and fillForm).
|
||||
if (Services.appinfo.processType ===
|
||||
Services.appinfo.PROCESS_TYPE_DEFAULT) {
|
||||
Services.obs.addObserver(this._observer, "passwordmgr-storage-replace",
|
||||
|
@ -577,21 +575,6 @@ LoginManager.prototype = {
|
|||
|
||||
return this._getPasswordOrigin(uriString, true);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* fillForm
|
||||
*
|
||||
* Fill the form with login information if we can find it.
|
||||
*/
|
||||
fillForm : function (form) {
|
||||
log("fillForm processing form[ id:", form.id, "]");
|
||||
return LoginManagerContent._asyncFindLogins(form, { showMasterPassword: true })
|
||||
.then(function({ form, loginsFound }) {
|
||||
return LoginManagerContent._fillForm(form, true, false, false, loginsFound)[0];
|
||||
});
|
||||
},
|
||||
|
||||
}; // end of LoginManager implementation
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManager]);
|
||||
|
|
Загрузка…
Ссылка в новой задаче