Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix FXIOS-8779 - Login autofill not working on certain websites #19490

Merged
merged 2 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ class CredentialAutofillCoordinator: BaseCoordinator {

let viewController = SelfSizingHostingController(rootView: loginAutofillView)

viewController.controllerWillDismiss = { [weak self] in
guard let currentTab = self?.tabManager.selectedTab else { return }
LoginsHelper.yieldFocusBackToField(with: currentTab)
}
Comment on lines +158 to +161
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@razvanlitianu to doublecheck


var bottomSheetViewModel = BottomSheetViewModel(closeButtonA11yLabel: .CloseButtonTitle)
bottomSheetViewModel.shouldDismissForTapOutside = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ class LoginsHelper: TabContentScript {
object: .loginsAutofilled)
}

public static func yieldFocusBackToField(with tab: Tab) {
let jsFocusCallback = "window.__firefox__.logins.yieldFocusBackToField()"
tab.webView?.evaluateJavascriptInDefaultContentWorld(jsFocusCallback)
}

Comment on lines +341 to +345
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for FXIOS-8690. When we dismiss the bottom sheet we also want to yield focus back to the field and show the accessory view.

// MARK: Theming System
private func applyTheme(for views: UIView...) {
views.forEach { view in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,116 +28,31 @@ window.__firefox__.includeOnce("LoginsHelper", function() {
return Math.round(Math.random() * (Number.MAX_VALUE - Number.MIN_VALUE) + Number.MIN_VALUE).toString()
},

// We need to keep track of the field that was focused by the user
// before the accessory view is clicked so we can yield back the focus
// when we autofill or cancel the bottomsheet. Webkit doesn't yield
// focus back to the specific field but rather to the top view.
activeField: null,

_messages: [ "RemoteLogins:loginsFound" ],

// Map from form login requests to information about that request.
_requests: { },

_takeRequest: function(msg) {
var data = msg;
var request = this._requests[data.requestId];
this._requests[data.requestId] = undefined;
return request;
},

_sendRequest: function(requestData, messageData) {
var requestId = this._getRandomId();
messageData.requestId = requestId;
webkit.messageHandlers.loginsManagerMessageHandler.postMessage(messageData);

var self = this;
return new Promise(function(resolve, reject) {
requestData.promise = { resolve: resolve, reject: reject };
self._requests[requestId] = requestData;
});
},

receiveMessage: function (msg) {
var request = this._takeRequest(msg);
switch (msg.name) {
case "RemoteLogins:loginsFound": {
request.promise.resolve({ form: request.form,
loginsFound: msg.logins});
console.log("ooooo ---- here ?? ", this.activeField.form, this.activeField)
this.loginsFound(this.activeField.form, msg.logins);
break;
}

case "RemoteLogins:loginsAutoCompleted": {
request.promise.resolve(msg.logins);
break;
}
}
},

_asyncFindLogins : function (form, options) {
// XXX - Unlike desktop, I want to avoid doing a lookup if there is no username/password in this form
var fields = this._getFormFields(form, false);
if (!fields[0] || !fields[1]) {
return Promise.reject("No logins found");
}

fields[0].addEventListener("blur", onBlur)

var formOrigin = LoginUtils._getPasswordOrigin(form.ownerDocument.documentURI);
var actionOrigin = LoginUtils._getActionOrigin(form);
if (actionOrigin == null) {
return Promise.reject("Action origin is null")
}

// XXX - Allowing the page to set origin information in this message is a security problem. Right now its just ignored...
// TODO: We need to designate what type of message we're sending here...
var requestData = { form: form };
var messageData = { type: "request", formOrigin: formOrigin, actionOrigin: actionOrigin };
return this._sendRequest(requestData, messageData);
},

loginsFound : function (form, loginsFound) {
var autofillForm = gAutofillForms; // && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
this._fillForm(form, autofillForm, false, false, false, loginsFound);
},

/*
* onUsernameInput
*
* Listens for DOMAutoComplete and blur events on an input field.
*/
onUsernameInput : function(event) {
if (!gEnabled)
return;

var acInputField = event.target;

// This is probably a bit over-conservatative.
if (!(acInputField.ownerDocument instanceof HTMLDocument))
return;

if (!this._isUsernameFieldType(acInputField))
return;

var acForm = acInputField.form;
if (!acForm)
return;

// If the username is blank, bail out now -- we don't want
// fillForm() to try filling in a login without a username
// to filter on (bug 471906).
if (!acInputField.value)
return;

log("onUsernameInput from", event.type);

// Make sure the username field fillForm will use is the
// same field as the autocomplete was activated on.
var [usernameField, passwordField, ignored] =
this._getFormFields(acForm, false);
if (usernameField == acInputField && passwordField) {
var self = this;
this._asyncFindLogins(acForm, { showMasterPassword: false })
.then(function(res) {
self._fillForm(res.form, true, true, true, true, res.loginsFound);
}).then(null, log);
} else {
// Ignore the event, it's for some input we don't care about.
}
this._fillForm(form, autofillForm, true, false, false, loginsFound);
},

/*
Expand Down Expand Up @@ -372,6 +287,16 @@ window.__firefox__.includeOnce("LoginsHelper", function() {
});
},

// TODO(issam): Merge this with .setUserInput for form autofill and use that instead.
fillValue(field, value) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(field, value);

["input", "change", "blur"].forEach(eventName => {
field.dispatchEvent(new Event(eventName, { bubbles: true }));
});
},

/*
* _fillform
*
Expand Down Expand Up @@ -452,18 +377,6 @@ window.__firefox__.includeOnce("LoginsHelper", function() {
// notification. See the _notifyFoundLogins docs for possible values.
var didntFillReason = null;

// Attach autocomplete stuff to the username field, if we have
// one. This is normally used to select from multiple accounts,
// but even with one account we should refill if the user edits.
// if (usernameField)
// formFillService.markAsLoginManagerField(usernameField);

// Don't clobber an existing password.
if (passwordField.value && !clobberPassword) {
didntFillReason = "existingPassword";
return [false, foundLogins];
}

// If the form has an autocomplete=off attribute in play, don't
// fill in the login automatically. We check this after attaching
// the autocomplete stuff to the username field, so the user can
Expand Down Expand Up @@ -495,26 +408,18 @@ window.__firefox__.includeOnce("LoginsHelper", function() {
var userEnteredDifferentCase = userTriggered && userNameDiffers && usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase();

if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) {
usernameField.value = selectedLogin.username;
this.fillValue(usernameField, selectedLogin.username);
dispatchKeyboardEvent(usernameField, "keydown", KEYCODE_ARROW_DOWN);
dispatchKeyboardEvent(usernameField, "keyup", KEYCODE_ARROW_DOWN);
if (document.activeElement !== usernameField) {
// Fire 'change' event to notify client-side validation on this field.
usernameField.dispatchEvent(new Event("change"));
}
// When the keyboard steals focus and gives it back,
// focusin is not triggered on the input it yields focus back to.
usernameField.focus();
}
}
if (passwordField.value != selectedLogin.password) {
passwordField.value = selectedLogin.password;
this.fillValue(passwordField, selectedLogin.password);
dispatchKeyboardEvent(passwordField, "keydown", KEYCODE_ARROW_DOWN);
dispatchKeyboardEvent(passwordField, "keyup", KEYCODE_ARROW_DOWN);
if (document.activeElement !== passwordField) {
// Fire 'change' event to notify client-side validation on this field.
passwordField.dispatchEvent(new Event("change"));
}
// When the keyboard steals focus and gives it back,
// focusin is not triggered on the input it yields focus back to.
passwordField.focus();
Expand All @@ -539,6 +444,12 @@ window.__firefox__.includeOnce("LoginsHelper", function() {
},
}


function yieldFocusBackToField() {
LoginManagerContent.activeField?.blur();
LoginManagerContent.activeField?.focus();
}

// define the field types for focus events
const FocusFieldType = {
username: "username",
Expand All @@ -553,14 +464,15 @@ window.__firefox__.includeOnce("LoginsHelper", function() {

const [username, password] = LoginManagerContent._getFormFields(form, false);
if (password) {
LoginManagerContent.activeField = event.target;
webkit.messageHandlers.loginsManagerMessageHandler.postMessage({
type: "fieldType",
fieldType: event.target === username ? FocusFieldType.username : FocusFieldType.password,
});
}
}

document.addEventListener("focusin", (ev) => onFocusIn(ev));
document.addEventListener("focusin", (ev) => onFocusIn(ev), {capture: true});

var LoginUtils = {
/*
Expand All @@ -584,9 +496,6 @@ window.__firefox__.includeOnce("LoginsHelper", function() {
},
}

function onBlur(event) {
LoginManagerContent.onUsernameInput(event)
}

window.addEventListener("submit", function(event) {
try {
Expand All @@ -606,6 +515,7 @@ window.__firefox__.includeOnce("LoginsHelper", function() {
// alert(ex);
}
};
this.yieldFocusBackToField = yieldFocusBackToField;
}

Object.defineProperty(window.__firefox__, "logins", {
Expand Down