Skip to content

[Autofill] ios credit card support and overall cc fixes #801

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

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
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
65 changes: 38 additions & 27 deletions dist/autofill-debug.js

Large diffs are not rendered by default.

65 changes: 38 additions & 27 deletions dist/autofill.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/CredentialsImport.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class CredentialsImport {
isAvailable() {
// Ideally we should also be checking activeForm?.isLogin or activeForm?.isHybrid, however
// in some instance activeForm is not yet initialized (when decorating the page).
return this.device.settings.availableInputTypes.credentialsImport;
return Boolean(this.device.settings.availableInputTypes.credentialsImport);
}

init() {
Expand Down
46 changes: 30 additions & 16 deletions src/Form/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ class Form {
}

// On mobile, we don't trigger on focus, so here we get the target control on label click
const isLabel = e.target instanceof HTMLLabelElement;
const isLabel = e.type !== 'focus' && e.target instanceof HTMLLabelElement;
const input = isLabel ? e.target.control : e.target;
if (!input || !this.inputs.all.has(input)) return;

Expand Down Expand Up @@ -795,38 +795,42 @@ class Form {
};

const isMobileApp = this.device.globalConfig.isMobileApp;
if (!(input instanceof HTMLSelectElement)) {
const events = ['pointerdown'];
if (!isMobileApp) events.push('focus');
if (input instanceof HTMLSelectElement) {
this.addListener(input, 'change', handlerSelect);
input.labels?.forEach((label) => {
this.addListener(label, 'pointerdown', isMobileApp ? handlerSelect : handlerLabel);
});
} else {
const events = ['pointerdown', 'focus'];
input.labels?.forEach((label) => {
// On mobile devices: handle click events (instead of focus) for labels,
// On desktop devices: handle label clicks which is needed when the form
// is in an iframe.
this.addListener(label, 'pointerdown', isMobileApp ? handler : handlerLabel);
});
events.forEach((ev) => this.addListener(input, ev, handler));
} else {
this.addListener(input, 'change', handlerSelect);
input.labels?.forEach((label) => {
this.addListener(label, 'pointerdown', isMobileApp ? handlerSelect : handlerLabel);
});
}
return this;
}

/**
* @param {MouseEvent} e
* @param {HTMLInputElement} input
* @returns {boolean}
*/
shouldOpenTooltip(e, input) {
if (!isPotentiallyViewable(input)) return false;

// Always open if the user has clicked on the Dax icon
if (isEventWithinDax(e, input)) return true;
if (this.device.globalConfig.isWindows) return true;

const mainType = getInputMainType(input);
const subtype = getInputSubtype(input);
const variant = getInputVariant(input);
const isIncontextSignupAvailable = this.device.inContextSignup?.isAvailable(subtype);

if (this.device.globalConfig.isApp) {
const mainType = getInputMainType(input);
// Check if, without in-context signup (passed as `null` below),
// we'd have any other items to show. This lets us know if we're
// just showing in-context signup, or with other autofill items.
Expand All @@ -841,11 +845,16 @@ class Form {
}
}

if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) {
const isMobileApp = this.device.globalConfig.isMobileApp;
if (this.device.globalConfig.isExtension || isMobileApp) {
// Don't open the tooltip on input focus whenever it's showing in-context signup
if (isIncontextSignupAvailable) return false;
}

// On ios, always send the calls to the native side, so they can decide on the UX
// On ios we either show a tooltip or the keyboard extension which non-blocking.
if (this.device.globalConfig.isIOS && mainType === 'creditCards') return true;

return !this.touched.has(input) && !input.classList.contains('ddg-autofilled');
}

Expand Down Expand Up @@ -878,8 +887,11 @@ class Form {

if (this.shouldSkipInput(input, dataType)) return;

// If the value is already there, just return
if (input.value === string) return;
// If the value is already filled, blur and return. We nede to blur to force closing the keyboard in some cases (e.g credit cards)
if (input.value === string) {
input.blur();
return;
}

const successful = setValue(input, string, this.device.globalConfig);

Expand Down Expand Up @@ -984,15 +996,15 @@ class Form {
return isLoginOrHybrid && this.device.credentialsImport.isAvailable();
}

getFirstViableCredentialsInput() {
getFirstViableInputForCredentials() {
return [...this.inputs.credentials].find((input) => canBeInteractedWith(input) && isPotentiallyViewable(input));
}

async promptLoginIfNeeded() {
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand why we're doing it here. This method is about autoprompting for login forms (lacks a comment, ouch), whereas the change we're looking at should be done after the initial manual prompt of a credit card prompt.

if (document.visibilityState !== 'visible' || !this.isLogin) return;

const firstCredentialInput = this.getFirstViableCredentialsInput();
const input = this.activeInput || firstCredentialInput;
const firstViableInput = this.getFirstViableInputForCredentials();
const input = this.activeInput || firstViableInput;
if (!input) return;

const mainType = getInputMainType(input);
Expand All @@ -1012,7 +1024,9 @@ class Form {
const elVCenter = y + height / 2;
// This checks that the form is not covered by anything else
const topMostElementFromPoint = document.elementFromPoint(elHCenter, elVCenter);

if (this.form.contains(topMostElementFromPoint)) {
// Add inputs to the touched set only for the dataTypeForExec, which currentl
this.execOnInputs((input) => {
if (isPotentiallyViewable(input)) {
this.touched.add(input);
Expand Down
2 changes: 1 addition & 1 deletion src/Form/inputTypeConfig.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { daxBase64, daxGrayscaleBase64 } from './logo-svg.js';
import * as ddgPasswordIcons from '../UI/img/ddgPasswordIcon.js';
import * as ddgPasswordIcons from '../UI/img/ddgInputIcons.js';
import { getInputType, getMainTypeFromType, getInputSubtype, getInputMainType, getInputVariant } from './matching.js';
import { createCredentialsTooltipItem } from '../InputTypes/Credentials.js';
import { CreditCardTooltipItem } from '../InputTypes/CreditCard.js';
Expand Down
19 changes: 10 additions & 9 deletions src/Form/matching-config/__generated__/compiled-matching-config.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/Form/matching-config/matching-config-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,14 @@ const matchingConfiguration = {
},

// CC
cardName: { match: '(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)' },
cardName: { match: '(card.*name|name.*card)|(card(.*)?holder|holder.*card)|(card.*owner|owner.*card)', skip: 'email' },
cardNumber: { match: 'card.*number|number.*card', skip: 'phone', forceUnknown: 'plus' },
cardSecurityCode: { match: 'security.?code|card.?verif|cvv|csc|cvc|cv2|card id' },
expirationMonth: {
match: '(card|\\bcc\\b)?.?(exp(iry|iration)?)?.?(month|\\bmm\\b(?![.\\s/-]yy))',
skip: 'mm[/\\s.\\-_—–]|check',
skip: 'mm[/\\s.\\-_—–]|check|year',
},
expirationYear: { match: '(card|\\bcc\\b)?.?(exp(iry|iration)?)?.?(year|yy)', skip: 'mm[/\\s.\\-_—–]|check' },
expirationYear: { match: '(card|\\bcc\\b)?.?(exp(iry|iration)?)?.?(year|yy)', skip: 'mm[/\\s.\\-_—–]|check|month' },
expiration: {
match: '(\\bmm\\b|\\b\\d\\d\\b)[/\\s.\\-_—–](\\byy|\\bjj|\\baa|\\b\\d\\d)|\\bexp|\\bvalid(idity| through| until)',
skip: 'invalid|^dd/|check',
Expand Down
19 changes: 17 additions & 2 deletions src/Form/matching-config/selectors-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ input[name="ccname" i],
input[name="cc-name" i],
input[name="ppw-accountHolderName" i],
input[name="payment[name]"],
input[id="cc-name" i],
input[id="ccname" i],
input[id*=cardname i],
input[id*=card-name i],
input[id*=card_name i]`;
Expand All @@ -127,9 +129,11 @@ input[autocomplete="cardnumber" i],
input[autocomplete="card-number" i],
input[name="ccnumber" i],
input[name="cc-number" i],
input[name*=card i][name*=number i]:not([name*=verif i]):not([name*=phone i]),
input[name*=card i][name*=number i]:not([name*=verif i]):not([name*=phone i]):not([name*=secur i]),
input[name*=cardnumber i],
input[name="payment[card_no]"],
input[id="cc-number" i],
input[id="ccnumber" i],
input[id*=cardnumber i],
input[id*=card-number i],
input[id*=card_number i]`;
Expand All @@ -144,7 +148,11 @@ input[name*="cvv" i],
input[name="cc-cvc" i],
input[name="cc-csc" i],
input[name="csc" i],
input[name*=security i][name*=code i]`;
input[name*=security i][name*=code i],
input[id="cc-csc" i],
input[id="csc" i],
input[id="cc-cvc" i],
input[id="cvc" i]`;

const expirationMonth = `
[autocomplete="cc-exp-month" i],
Expand All @@ -158,6 +166,8 @@ const expirationMonth = `
[name*=cc-exp-month i],
[name*="card_exp-month" i],
[name*=cc_exp_month i],
[id="cc-exp-month" i],
[id="cc_exp_month" i],
[id*=cc-month i]`;

const expirationYear = `
Expand All @@ -172,13 +182,18 @@ const expirationYear = `
[name*="cc-exp-year" i],
[name*="card_exp-year" i],
[name*=cc_exp_year i],
[id="cc-exp-year" i],
[id="cc_exp_year" i],
[id*=cc-year i]`;

const expiration = `
[autocomplete="cc-exp" i],
[name="cc-exp" i],
[name="exp-date" i],
input[name="expiry" i],
[name="expirationDate" i],
input[name*=ex][placeholder="mm/yy" i],
[id="cc-exp" i],
input[id*=expiration i]`;

const firstName = `
Expand Down
1 change: 1 addition & 0 deletions src/UI/controllers/NativeUIController.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class NativeUIController extends UIController {
form.activeInput?.focus();
break;
}

case 'acceptGeneratedPassword': {
form.autofillData(
{
Expand Down
Loading
Loading