From 71bc2d414e06bef1ef1213bc7380eb01ec6a3ac5 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 13 Dec 2022 10:19:45 +0100 Subject: [PATCH 01/15] Updates FormAnalyzer naming and adds comments Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 50 ++++++++++++++----- dist/autofill.js | 50 ++++++++++++++----- src/Form/FormAnalyzer.js | 46 ++++++++++++----- .../Resources/assets/autofill-debug.js | 50 ++++++++++++++----- swift-package/Resources/assets/autofill.js | 50 ++++++++++++++----- 5 files changed, 181 insertions(+), 65 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index d2339630e..e90a9a8e8 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -10114,10 +10114,10 @@ var _autofillUtils = require("../autofill-utils.js"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -const negativeRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); -const positiveRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); -const conservativePositiveRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictPositiveRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); +const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); +const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -10136,7 +10136,17 @@ class FormAnalyzer { this.form = form; this.matching = matching || new _matching.Matching(_matchingConfiguration.matchingConfiguration); + /** + * The signal is a continuum where negative values imply login and positive imply signup + * @type {number} + */ + this.autofillSignal = 0; + /** + * Collects the signals for debugging purposes + * @type {string[]} + */ + this.signals = []; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { @@ -10155,12 +10165,26 @@ class FormAnalyzer { get isSignup() { return this.autofillSignal >= 0; } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseSignalBy(strength, signal) { this.autofillSignal += strength; this.signals.push("".concat(signal, ": +").concat(strength)); return this; } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + decreaseSignalBy(strength, signal) { this.autofillSignal -= strength; @@ -10168,7 +10192,7 @@ class FormAnalyzer { return this; } /** - * + * Updates the Login<->Signup signal according to the provided parameters * @param {object} p * @param {string} p.string - The string to check * @param {number} p.strength - Strength of the signal @@ -10189,22 +10213,22 @@ class FormAnalyzer { shouldCheckUnifiedForm = false, shouldBeConservative = false } = _ref; - const matchesNegative = string === 'current-password' || negativeRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal + const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal - if (shouldCheckUnifiedForm && matchesNegative && strictPositiveRegex.test(string)) { + if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); return this; } - const positiveRegexToUse = shouldBeConservative ? conservativePositiveRegex : positiveRegex; - const matchesPositive = string === 'new-password' || positiveRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside + const signupRegexToUse = shouldBeConservative ? conservativeSignupRegex : signupRegex; + const matchesSignup = string === 'new-password' || signupRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside if (shouldFlip) { - if (matchesNegative) this.increaseSignalBy(strength, signalType); - if (matchesPositive) this.decreaseSignalBy(strength, signalType); + if (matchesLogin) this.increaseSignalBy(strength, signalType); + if (matchesSignup) this.decreaseSignalBy(strength, signalType); } else { - if (matchesNegative) this.decreaseSignalBy(strength, signalType); - if (matchesPositive) this.increaseSignalBy(strength, signalType); + if (matchesLogin) this.decreaseSignalBy(strength, signalType); + if (matchesSignup) this.increaseSignalBy(strength, signalType); } return this; diff --git a/dist/autofill.js b/dist/autofill.js index 8fd616da5..f24b08088 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -6438,10 +6438,10 @@ var _autofillUtils = require("../autofill-utils.js"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -const negativeRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); -const positiveRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); -const conservativePositiveRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictPositiveRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); +const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); +const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -6460,7 +6460,17 @@ class FormAnalyzer { this.form = form; this.matching = matching || new _matching.Matching(_matchingConfiguration.matchingConfiguration); + /** + * The signal is a continuum where negative values imply login and positive imply signup + * @type {number} + */ + this.autofillSignal = 0; + /** + * Collects the signals for debugging purposes + * @type {string[]} + */ + this.signals = []; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { @@ -6479,12 +6489,26 @@ class FormAnalyzer { get isSignup() { return this.autofillSignal >= 0; } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseSignalBy(strength, signal) { this.autofillSignal += strength; this.signals.push("".concat(signal, ": +").concat(strength)); return this; } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + decreaseSignalBy(strength, signal) { this.autofillSignal -= strength; @@ -6492,7 +6516,7 @@ class FormAnalyzer { return this; } /** - * + * Updates the Login<->Signup signal according to the provided parameters * @param {object} p * @param {string} p.string - The string to check * @param {number} p.strength - Strength of the signal @@ -6513,22 +6537,22 @@ class FormAnalyzer { shouldCheckUnifiedForm = false, shouldBeConservative = false } = _ref; - const matchesNegative = string === 'current-password' || negativeRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal + const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal - if (shouldCheckUnifiedForm && matchesNegative && strictPositiveRegex.test(string)) { + if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); return this; } - const positiveRegexToUse = shouldBeConservative ? conservativePositiveRegex : positiveRegex; - const matchesPositive = string === 'new-password' || positiveRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside + const signupRegexToUse = shouldBeConservative ? conservativeSignupRegex : signupRegex; + const matchesSignup = string === 'new-password' || signupRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside if (shouldFlip) { - if (matchesNegative) this.increaseSignalBy(strength, signalType); - if (matchesPositive) this.decreaseSignalBy(strength, signalType); + if (matchesLogin) this.increaseSignalBy(strength, signalType); + if (matchesSignup) this.decreaseSignalBy(strength, signalType); } else { - if (matchesNegative) this.decreaseSignalBy(strength, signalType); - if (matchesPositive) this.increaseSignalBy(strength, signalType); + if (matchesLogin) this.decreaseSignalBy(strength, signalType); + if (matchesSignup) this.increaseSignalBy(strength, signalType); } return this; diff --git a/src/Form/FormAnalyzer.js b/src/Form/FormAnalyzer.js index 9c8a4f914..0edab6070 100644 --- a/src/Form/FormAnalyzer.js +++ b/src/Form/FormAnalyzer.js @@ -3,12 +3,12 @@ import { constants } from '../constants.js' import { matchingConfiguration } from './matching-configuration.js' import { getText, isLikelyASubmitButton } from '../autofill-utils.js' -const negativeRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i) -const positiveRegex = new RegExp( +const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i) +const signupRegex = new RegExp( /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i ) -const conservativePositiveRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i) -const strictPositiveRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i) +const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i) +const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i) class FormAnalyzer { /** @type HTMLElement */ @@ -23,7 +23,15 @@ class FormAnalyzer { constructor (form, input, matching) { this.form = form this.matching = matching || new Matching(matchingConfiguration) + /** + * The signal is a continuum where negative values imply login and positive imply signup + * @type {number} + */ this.autofillSignal = 0 + /** + * Collects the signals for debugging purposes + * @type {string[]} + */ this.signals = [] // Avoid autofill on our signup page @@ -44,12 +52,24 @@ class FormAnalyzer { return this.autofillSignal >= 0 } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ increaseSignalBy (strength, signal) { this.autofillSignal += strength this.signals.push(`${signal}: +${strength}`) return this } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ decreaseSignalBy (strength, signal) { this.autofillSignal -= strength this.signals.push(`${signal}: -${strength}`) @@ -57,7 +77,7 @@ class FormAnalyzer { } /** - * + * Updates the Login<->Signup signal according to the provided parameters * @param {object} p * @param {string} p.string - The string to check * @param {number} p.strength - Strength of the signal @@ -75,24 +95,24 @@ class FormAnalyzer { shouldCheckUnifiedForm = false, shouldBeConservative = false }) { - const matchesNegative = string === 'current-password' || negativeRegex.test(string) + const matchesLogin = string === 'current-password' || loginRegex.test(string) // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal - if (shouldCheckUnifiedForm && matchesNegative && strictPositiveRegex.test(string)) { + if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { this.decreaseSignalBy(strength + 2, `Unified detected ${signalType}`) return this } - const positiveRegexToUse = shouldBeConservative ? conservativePositiveRegex : positiveRegex - const matchesPositive = string === 'new-password' || positiveRegexToUse.test(string) + const signupRegexToUse = shouldBeConservative ? conservativeSignupRegex : signupRegex + const matchesSignup = string === 'new-password' || signupRegexToUse.test(string) // In some cases a login match means the login is somewhere else, i.e. when a link points outside if (shouldFlip) { - if (matchesNegative) this.increaseSignalBy(strength, signalType) - if (matchesPositive) this.decreaseSignalBy(strength, signalType) + if (matchesLogin) this.increaseSignalBy(strength, signalType) + if (matchesSignup) this.decreaseSignalBy(strength, signalType) } else { - if (matchesNegative) this.decreaseSignalBy(strength, signalType) - if (matchesPositive) this.increaseSignalBy(strength, signalType) + if (matchesLogin) this.decreaseSignalBy(strength, signalType) + if (matchesSignup) this.increaseSignalBy(strength, signalType) } return this } diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index d2339630e..e90a9a8e8 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -10114,10 +10114,10 @@ var _autofillUtils = require("../autofill-utils.js"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -const negativeRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); -const positiveRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); -const conservativePositiveRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictPositiveRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); +const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); +const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -10136,7 +10136,17 @@ class FormAnalyzer { this.form = form; this.matching = matching || new _matching.Matching(_matchingConfiguration.matchingConfiguration); + /** + * The signal is a continuum where negative values imply login and positive imply signup + * @type {number} + */ + this.autofillSignal = 0; + /** + * Collects the signals for debugging purposes + * @type {string[]} + */ + this.signals = []; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { @@ -10155,12 +10165,26 @@ class FormAnalyzer { get isSignup() { return this.autofillSignal >= 0; } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseSignalBy(strength, signal) { this.autofillSignal += strength; this.signals.push("".concat(signal, ": +").concat(strength)); return this; } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + decreaseSignalBy(strength, signal) { this.autofillSignal -= strength; @@ -10168,7 +10192,7 @@ class FormAnalyzer { return this; } /** - * + * Updates the Login<->Signup signal according to the provided parameters * @param {object} p * @param {string} p.string - The string to check * @param {number} p.strength - Strength of the signal @@ -10189,22 +10213,22 @@ class FormAnalyzer { shouldCheckUnifiedForm = false, shouldBeConservative = false } = _ref; - const matchesNegative = string === 'current-password' || negativeRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal + const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal - if (shouldCheckUnifiedForm && matchesNegative && strictPositiveRegex.test(string)) { + if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); return this; } - const positiveRegexToUse = shouldBeConservative ? conservativePositiveRegex : positiveRegex; - const matchesPositive = string === 'new-password' || positiveRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside + const signupRegexToUse = shouldBeConservative ? conservativeSignupRegex : signupRegex; + const matchesSignup = string === 'new-password' || signupRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside if (shouldFlip) { - if (matchesNegative) this.increaseSignalBy(strength, signalType); - if (matchesPositive) this.decreaseSignalBy(strength, signalType); + if (matchesLogin) this.increaseSignalBy(strength, signalType); + if (matchesSignup) this.decreaseSignalBy(strength, signalType); } else { - if (matchesNegative) this.decreaseSignalBy(strength, signalType); - if (matchesPositive) this.increaseSignalBy(strength, signalType); + if (matchesLogin) this.decreaseSignalBy(strength, signalType); + if (matchesSignup) this.increaseSignalBy(strength, signalType); } return this; diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 8fd616da5..f24b08088 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -6438,10 +6438,10 @@ var _autofillUtils = require("../autofill-utils.js"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -const negativeRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); -const positiveRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); -const conservativePositiveRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictPositiveRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); +const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); +const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -6460,7 +6460,17 @@ class FormAnalyzer { this.form = form; this.matching = matching || new _matching.Matching(_matchingConfiguration.matchingConfiguration); + /** + * The signal is a continuum where negative values imply login and positive imply signup + * @type {number} + */ + this.autofillSignal = 0; + /** + * Collects the signals for debugging purposes + * @type {string[]} + */ + this.signals = []; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { @@ -6479,12 +6489,26 @@ class FormAnalyzer { get isSignup() { return this.autofillSignal >= 0; } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseSignalBy(strength, signal) { this.autofillSignal += strength; this.signals.push("".concat(signal, ": +").concat(strength)); return this; } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + decreaseSignalBy(strength, signal) { this.autofillSignal -= strength; @@ -6492,7 +6516,7 @@ class FormAnalyzer { return this; } /** - * + * Updates the Login<->Signup signal according to the provided parameters * @param {object} p * @param {string} p.string - The string to check * @param {number} p.strength - Strength of the signal @@ -6513,22 +6537,22 @@ class FormAnalyzer { shouldCheckUnifiedForm = false, shouldBeConservative = false } = _ref; - const matchesNegative = string === 'current-password' || negativeRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal + const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal - if (shouldCheckUnifiedForm && matchesNegative && strictPositiveRegex.test(string)) { + if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); return this; } - const positiveRegexToUse = shouldBeConservative ? conservativePositiveRegex : positiveRegex; - const matchesPositive = string === 'new-password' || positiveRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside + const signupRegexToUse = shouldBeConservative ? conservativeSignupRegex : signupRegex; + const matchesSignup = string === 'new-password' || signupRegexToUse.test(string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside if (shouldFlip) { - if (matchesNegative) this.increaseSignalBy(strength, signalType); - if (matchesPositive) this.decreaseSignalBy(strength, signalType); + if (matchesLogin) this.increaseSignalBy(strength, signalType); + if (matchesSignup) this.decreaseSignalBy(strength, signalType); } else { - if (matchesNegative) this.decreaseSignalBy(strength, signalType); - if (matchesPositive) this.increaseSignalBy(strength, signalType); + if (matchesLogin) this.decreaseSignalBy(strength, signalType); + if (matchesSignup) this.increaseSignalBy(strength, signalType); } return this; From 332b98188029441824f0d1a98fe1447e5dc97e6a Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 13 Dec 2022 15:42:01 +0100 Subject: [PATCH 02/15] Minor type improvement Signed-off-by: Emanuele Feliziani --- src/Form/inputTypeConfig.js | 2 +- src/UI/HTMLTooltip.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Form/inputTypeConfig.js b/src/Form/inputTypeConfig.js index e6a6ee0eb..527b489fe 100644 --- a/src/Form/inputTypeConfig.js +++ b/src/Form/inputTypeConfig.js @@ -140,7 +140,7 @@ const getInputConfig = (input) => { /** * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes | string} inputType + * @param {import('./matching').SupportedTypes} inputType * @returns {InputTypeConfigs} */ const getInputConfigFromType = (inputType) => { diff --git a/src/UI/HTMLTooltip.test.js b/src/UI/HTMLTooltip.test.js index b4b48a529..66ab9ba24 100644 --- a/src/UI/HTMLTooltip.test.js +++ b/src/UI/HTMLTooltip.test.js @@ -31,7 +31,7 @@ describe('HTMLTooltip', () => { * @link {https://app.asana.com/0/1177771139624306/1202412384393015/f} */ it('works with default values', () => { - const config = getInputConfigFromType('credentials') + const config = getInputConfigFromType('credentials.username') const tooltip = new HTMLTooltip(config, 'credentials.username', () => {}, defaultOptions) const spy = jest.spyOn(tooltip, 'setupSizeListener') tooltip.init() From 62f0775ba1d678b9fddba2a9549e3d2e304046f0 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Thu, 15 Dec 2022 14:37:08 +0100 Subject: [PATCH 03/15] Show identities if no credentials on login form Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 61 ++++++++++++++++--- dist/autofill.js | 61 ++++++++++++++++--- integration-test/helpers/mocks.js | 4 ++ integration-test/helpers/pages.js | 18 +++++- .../tests/login-form.macos.spec.js | 20 ++++++ src/DeviceInterface/InterfacePrototype.js | 2 +- src/Form/Form.js | 28 ++++++++- src/Form/input-classifiers.test.js | 6 +- src/Form/matching.js | 22 ++++++- .../Resources/assets/autofill-debug.js | 61 ++++++++++++++++--- swift-package/Resources/assets/autofill.js | 61 ++++++++++++++++--- 11 files changed, 300 insertions(+), 44 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index e90a9a8e8..c19a3c348 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -8906,7 +8906,7 @@ class InterfacePrototype { } // Redecorate fields according to the new types - this.scanner.forms.forEach(form => form.redecorateAllInputs()); + this.scanner.forms.forEach(form => form.recategorizeAllInputs()); } catch (e) { if (this.globalConfig.isDDGTestMode) { console.log('isDDGTestMode: providerStatusUpdated error: ❌', e); @@ -9510,7 +9510,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const { - ATTR_AUTOFILL + ATTR_AUTOFILL, + ATTR_INPUT_TYPE } = _constants.constants; class Form { @@ -9769,6 +9770,27 @@ class Form { } }); } + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + + + forgetAllInputs() { + this.execOnInputs(input => { + input.removeAttribute(ATTR_INPUT_TYPE); + }); + Object.values(this.inputs).forEach(inputSet => inputSet.clear()); + } + /** + * Resets our input scoring and starts from scratch + */ + + + recategorizeAllInputs() { + this.removeAllDecorations(); + this.forgetAllInputs(); + this.categorizeInputs(); + } resetAllInputs() { this.execOnInputs(input => { @@ -9848,13 +9870,18 @@ class Form { } addInput(input) { + var _this$device$settings; + // Nothing to do with 1-character fields if (input.maxLength === 1) return this; if (this.inputs.all.has(input)) return this; this.inputs.all.add(input); - this.matching.setInputType(input, this.form, { - isLogin: this.isLogin - }); + const opts = { + isLogin: this.isLogin, + hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), + isMobile: this.device.globalConfig.isMobileApp + }; + this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); this.inputs[mainInputType].add(input); this.decorateInput(input); @@ -11567,7 +11594,7 @@ const getInputConfig = input => { }; /** * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes | string} inputType + * @param {import('./matching').SupportedTypes} inputType * @returns {InputTypeConfigs} */ @@ -12593,7 +12620,7 @@ class Matching { * * @param {HTMLInputElement|HTMLSelectElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedTypes} */ @@ -12623,7 +12650,15 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - return opts.isLogin ? 'credentials.username' : 'identities.emailAddress'; + if (opts.isLogin) { + if (!opts.isMobile && !opts.hasCredentials) { + return 'identities.emailAddress'; + } + + return 'credentials.username'; + } + + return 'identities.emailAddress'; } if (this.subtypeFromMatchers('username', input)) { @@ -12639,11 +12674,19 @@ class Matching { return 'unknown'; } + /** + * @typedef {{ + * isLogin?: boolean, + * hasCredentials?: boolean, + * isMobile?: boolean + * }} SetInputTypeOpts + */ + /** * Sets the input type as a data attribute to the element and returns it * @param {HTMLInputElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedSubTypes | string} */ diff --git a/dist/autofill.js b/dist/autofill.js index f24b08088..4b8718ac0 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -5230,7 +5230,7 @@ class InterfacePrototype { } // Redecorate fields according to the new types - this.scanner.forms.forEach(form => form.redecorateAllInputs()); + this.scanner.forms.forEach(form => form.recategorizeAllInputs()); } catch (e) { if (this.globalConfig.isDDGTestMode) { console.log('isDDGTestMode: providerStatusUpdated error: ❌', e); @@ -5834,7 +5834,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const { - ATTR_AUTOFILL + ATTR_AUTOFILL, + ATTR_INPUT_TYPE } = _constants.constants; class Form { @@ -6093,6 +6094,27 @@ class Form { } }); } + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + + + forgetAllInputs() { + this.execOnInputs(input => { + input.removeAttribute(ATTR_INPUT_TYPE); + }); + Object.values(this.inputs).forEach(inputSet => inputSet.clear()); + } + /** + * Resets our input scoring and starts from scratch + */ + + + recategorizeAllInputs() { + this.removeAllDecorations(); + this.forgetAllInputs(); + this.categorizeInputs(); + } resetAllInputs() { this.execOnInputs(input => { @@ -6172,13 +6194,18 @@ class Form { } addInput(input) { + var _this$device$settings; + // Nothing to do with 1-character fields if (input.maxLength === 1) return this; if (this.inputs.all.has(input)) return this; this.inputs.all.add(input); - this.matching.setInputType(input, this.form, { - isLogin: this.isLogin - }); + const opts = { + isLogin: this.isLogin, + hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), + isMobile: this.device.globalConfig.isMobileApp + }; + this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); this.inputs[mainInputType].add(input); this.decorateInput(input); @@ -7891,7 +7918,7 @@ const getInputConfig = input => { }; /** * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes | string} inputType + * @param {import('./matching').SupportedTypes} inputType * @returns {InputTypeConfigs} */ @@ -8917,7 +8944,7 @@ class Matching { * * @param {HTMLInputElement|HTMLSelectElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedTypes} */ @@ -8947,7 +8974,15 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - return opts.isLogin ? 'credentials.username' : 'identities.emailAddress'; + if (opts.isLogin) { + if (!opts.isMobile && !opts.hasCredentials) { + return 'identities.emailAddress'; + } + + return 'credentials.username'; + } + + return 'identities.emailAddress'; } if (this.subtypeFromMatchers('username', input)) { @@ -8963,11 +8998,19 @@ class Matching { return 'unknown'; } + /** + * @typedef {{ + * isLogin?: boolean, + * hasCredentials?: boolean, + * isMobile?: boolean + * }} SetInputTypeOpts + */ + /** * Sets the input type as a data attribute to the element and returns it * @param {HTMLInputElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedSubTypes | string} */ diff --git a/integration-test/helpers/mocks.js b/integration-test/helpers/mocks.js index e15d62781..6ec779be3 100644 --- a/integration-test/helpers/mocks.js +++ b/integration-test/helpers/mocks.js @@ -72,5 +72,9 @@ export const constants = { cardNumber: true }, email: true + }, + iconMatchers: { + key: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4w', + dax: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBo' } } diff --git a/integration-test/helpers/pages.js b/integration-test/helpers/pages.js index d6bf37862..9a374a0ed 100644 --- a/integration-test/helpers/pages.js +++ b/integration-test/helpers/pages.js @@ -177,11 +177,27 @@ export function loginPage (page, server, opts = {}) { expect(styles1).toContain('data:image/svg+xml;base64,') expect(styles2).toContain('data:image/svg+xml;base64,') }, + async emailFieldShowsDax () { + // don't make assertions until the element is both found + has a none-empty 'style' attribute + await page.waitForFunction(() => Boolean(document.querySelector('#email')?.getAttribute('style'))) + const emailStyle = await page.locator('#email').getAttribute('style') + expect(emailStyle).toContain(constants.iconMatchers.dax) + }, + async emailHasDaxPasswordNoIcon () { + await this.emailFieldShowsDax() + const passwordStyle = await page.locator('#password').getAttribute('style') + expect(passwordStyle || '').not.toContain('data:image/svg+xml;base64,') + }, + async emailHasDaxPasswordHasKey () { + await this.emailFieldShowsDax() + const passwordStyle = await page.locator('#password').getAttribute('style') + expect(passwordStyle || '').toContain(constants.iconMatchers.key) + }, async onlyPasswordFieldHasIcon () { const styles1 = await page.locator('#email').getAttribute('style') const styles2 = await page.locator('#password').getAttribute('style') expect(styles1 || '').not.toContain('data:image/svg+xml;base64,') - expect(styles2 || '').toContain('data:image/svg+xml;base64,') + expect(styles2 || '').toContain(constants.iconMatchers.key) }, /** * @param {string} username diff --git a/integration-test/tests/login-form.macos.spec.js b/integration-test/tests/login-form.macos.spec.js index dd2388f2e..ceb44bc07 100644 --- a/integration-test/tests/login-form.macos.spec.js +++ b/integration-test/tests/login-form.macos.spec.js @@ -187,6 +187,26 @@ test.describe('Auto-fill a login form on macOS', () => { await login.clickIntoUsernameInput() await login.fieldsDoNotContainIcons() }) + test('I should see Dax if Email Protection is enabled', async ({page}) => { + await forwardConsoleMessages(page) + await createWebkitMocks() + .withAvailableInputTypes({ + credentials: {username: false, password: false}, + email: true + }) + .withPersonalEmail(personalAddress) + .withPrivateEmail('random123@duck.com') + .applyTo(page) + + await createAutofillScript() + .replaceAll(macosContentScopeReplacements()) + .platform('macos') + .applyTo(page) + + const login = loginPage(page, server) + await login.navigate() + await login.emailHasDaxPasswordNoIcon() + }) }) }) diff --git a/src/DeviceInterface/InterfacePrototype.js b/src/DeviceInterface/InterfacePrototype.js index b78627b77..5fe27cd8c 100644 --- a/src/DeviceInterface/InterfacePrototype.js +++ b/src/DeviceInterface/InterfacePrototype.js @@ -574,7 +574,7 @@ class InterfacePrototype { this.removeTooltip() } // Redecorate fields according to the new types - this.scanner.forms.forEach(form => form.redecorateAllInputs()) + this.scanner.forms.forEach(form => form.recategorizeAllInputs()) } catch (e) { if (this.globalConfig.isDDGTestMode) { console.log('isDDGTestMode: providerStatusUpdated error: ❌', e) diff --git a/src/Form/Form.js b/src/Form/Form.js index 7991609bd..c7946ca86 100644 --- a/src/Form/Form.js +++ b/src/Form/Form.js @@ -23,7 +23,7 @@ import { } from './formatters.js' import {constants} from '../constants.js' -const {ATTR_AUTOFILL} = constants +const {ATTR_AUTOFILL, ATTR_INPUT_TYPE} = constants class Form { /** @type {import("../Form/matching").Matching} */ @@ -244,6 +244,25 @@ class Form { } }) } + + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + forgetAllInputs () { + this.execOnInputs((input) => { + input.removeAttribute(ATTR_INPUT_TYPE) + }) + Object.values(this.inputs).forEach((inputSet) => inputSet.clear()) + } + + /** + * Resets our input scoring and starts from scratch + */ + recategorizeAllInputs () { + this.removeAllDecorations() + this.forgetAllInputs() + this.categorizeInputs() + } resetAllInputs () { this.execOnInputs((input) => { setValue(input, '', this.device.globalConfig) @@ -327,7 +346,12 @@ class Form { this.inputs.all.add(input) - this.matching.setInputType(input, this.form, { isLogin: this.isLogin }) + const opts = { + isLogin: this.isLogin, + hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), + isMobile: this.device.globalConfig.isMobileApp + } + this.matching.setInputType(input, this.form, opts) const mainInputType = getInputMainType(input) this.inputs[mainInputType].add(input) diff --git a/src/Form/input-classifiers.test.js b/src/Form/input-classifiers.test.js index dd8cf12e5..711a5ae62 100644 --- a/src/Form/input-classifiers.test.js +++ b/src/Form/input-classifiers.test.js @@ -8,6 +8,7 @@ import InterfacePrototype from '../DeviceInterface/InterfacePrototype.js' import testCases from './test-cases/index.js' import {SUBMIT_BUTTON_SELECTOR} from './selectors-css.js' +import {createAvailableInputTypes} from '../../integration-test/helpers/utils.js' /** * @param {HTMLInputElement} el @@ -160,7 +161,10 @@ describe.each(testCases)('Test $html fields', (testCase) => { button._jsdomMockOffsetHeight = 50 }) - const scanner = createScanner(InterfacePrototype.default()) + const deviceInterface = InterfacePrototype.default() + const availableInputTypes = createAvailableInputTypes({credentials: {username: true, password: true}}) + deviceInterface.settings.setAvailableInputTypes(availableInputTypes) + const scanner = createScanner(deviceInterface) scanner.findEligibleInputs(document) const detectedSubmitButtons = Array.from(scanner.forms.values()).map(form => form.submitButtons).flat() diff --git a/src/Form/matching.js b/src/Form/matching.js index e2c357801..00bea5bc6 100644 --- a/src/Form/matching.js +++ b/src/Form/matching.js @@ -183,7 +183,7 @@ class Matching { * * @param {HTMLInputElement|HTMLSelectElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedTypes} */ inferInputType (input, formEl, opts = {}) { @@ -209,7 +209,15 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - return opts.isLogin ? 'credentials.username' : 'identities.emailAddress' + if (opts.isLogin) { + if (!opts.isMobile && !opts.hasCredentials) { + return 'identities.emailAddress' + } + + return 'credentials.username' + } + + return 'identities.emailAddress' } if (this.subtypeFromMatchers('username', input)) { @@ -226,11 +234,19 @@ class Matching { return 'unknown' } + /** + * @typedef {{ + * isLogin?: boolean, + * hasCredentials?: boolean, + * isMobile?: boolean + * }} SetInputTypeOpts + */ + /** * Sets the input type as a data attribute to the element and returns it * @param {HTMLInputElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedSubTypes | string} */ setInputType (input, formEl, opts = {}) { diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index e90a9a8e8..c19a3c348 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -8906,7 +8906,7 @@ class InterfacePrototype { } // Redecorate fields according to the new types - this.scanner.forms.forEach(form => form.redecorateAllInputs()); + this.scanner.forms.forEach(form => form.recategorizeAllInputs()); } catch (e) { if (this.globalConfig.isDDGTestMode) { console.log('isDDGTestMode: providerStatusUpdated error: ❌', e); @@ -9510,7 +9510,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const { - ATTR_AUTOFILL + ATTR_AUTOFILL, + ATTR_INPUT_TYPE } = _constants.constants; class Form { @@ -9769,6 +9770,27 @@ class Form { } }); } + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + + + forgetAllInputs() { + this.execOnInputs(input => { + input.removeAttribute(ATTR_INPUT_TYPE); + }); + Object.values(this.inputs).forEach(inputSet => inputSet.clear()); + } + /** + * Resets our input scoring and starts from scratch + */ + + + recategorizeAllInputs() { + this.removeAllDecorations(); + this.forgetAllInputs(); + this.categorizeInputs(); + } resetAllInputs() { this.execOnInputs(input => { @@ -9848,13 +9870,18 @@ class Form { } addInput(input) { + var _this$device$settings; + // Nothing to do with 1-character fields if (input.maxLength === 1) return this; if (this.inputs.all.has(input)) return this; this.inputs.all.add(input); - this.matching.setInputType(input, this.form, { - isLogin: this.isLogin - }); + const opts = { + isLogin: this.isLogin, + hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), + isMobile: this.device.globalConfig.isMobileApp + }; + this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); this.inputs[mainInputType].add(input); this.decorateInput(input); @@ -11567,7 +11594,7 @@ const getInputConfig = input => { }; /** * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes | string} inputType + * @param {import('./matching').SupportedTypes} inputType * @returns {InputTypeConfigs} */ @@ -12593,7 +12620,7 @@ class Matching { * * @param {HTMLInputElement|HTMLSelectElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedTypes} */ @@ -12623,7 +12650,15 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - return opts.isLogin ? 'credentials.username' : 'identities.emailAddress'; + if (opts.isLogin) { + if (!opts.isMobile && !opts.hasCredentials) { + return 'identities.emailAddress'; + } + + return 'credentials.username'; + } + + return 'identities.emailAddress'; } if (this.subtypeFromMatchers('username', input)) { @@ -12639,11 +12674,19 @@ class Matching { return 'unknown'; } + /** + * @typedef {{ + * isLogin?: boolean, + * hasCredentials?: boolean, + * isMobile?: boolean + * }} SetInputTypeOpts + */ + /** * Sets the input type as a data attribute to the element and returns it * @param {HTMLInputElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedSubTypes | string} */ diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index f24b08088..4b8718ac0 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -5230,7 +5230,7 @@ class InterfacePrototype { } // Redecorate fields according to the new types - this.scanner.forms.forEach(form => form.redecorateAllInputs()); + this.scanner.forms.forEach(form => form.recategorizeAllInputs()); } catch (e) { if (this.globalConfig.isDDGTestMode) { console.log('isDDGTestMode: providerStatusUpdated error: ❌', e); @@ -5834,7 +5834,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const { - ATTR_AUTOFILL + ATTR_AUTOFILL, + ATTR_INPUT_TYPE } = _constants.constants; class Form { @@ -6093,6 +6094,27 @@ class Form { } }); } + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + + + forgetAllInputs() { + this.execOnInputs(input => { + input.removeAttribute(ATTR_INPUT_TYPE); + }); + Object.values(this.inputs).forEach(inputSet => inputSet.clear()); + } + /** + * Resets our input scoring and starts from scratch + */ + + + recategorizeAllInputs() { + this.removeAllDecorations(); + this.forgetAllInputs(); + this.categorizeInputs(); + } resetAllInputs() { this.execOnInputs(input => { @@ -6172,13 +6194,18 @@ class Form { } addInput(input) { + var _this$device$settings; + // Nothing to do with 1-character fields if (input.maxLength === 1) return this; if (this.inputs.all.has(input)) return this; this.inputs.all.add(input); - this.matching.setInputType(input, this.form, { - isLogin: this.isLogin - }); + const opts = { + isLogin: this.isLogin, + hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), + isMobile: this.device.globalConfig.isMobileApp + }; + this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); this.inputs[mainInputType].add(input); this.decorateInput(input); @@ -7891,7 +7918,7 @@ const getInputConfig = input => { }; /** * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes | string} inputType + * @param {import('./matching').SupportedTypes} inputType * @returns {InputTypeConfigs} */ @@ -8917,7 +8944,7 @@ class Matching { * * @param {HTMLInputElement|HTMLSelectElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedTypes} */ @@ -8947,7 +8974,15 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - return opts.isLogin ? 'credentials.username' : 'identities.emailAddress'; + if (opts.isLogin) { + if (!opts.isMobile && !opts.hasCredentials) { + return 'identities.emailAddress'; + } + + return 'credentials.username'; + } + + return 'identities.emailAddress'; } if (this.subtypeFromMatchers('username', input)) { @@ -8963,11 +8998,19 @@ class Matching { return 'unknown'; } + /** + * @typedef {{ + * isLogin?: boolean, + * hasCredentials?: boolean, + * isMobile?: boolean + * }} SetInputTypeOpts + */ + /** * Sets the input type as a data attribute to the element and returns it * @param {HTMLInputElement} input * @param {HTMLElement} formEl - * @param {{isLogin?: boolean}} [opts] + * @param {SetInputTypeOpts} [opts] * @returns {SupportedSubTypes | string} */ From ce448701cf8c15cfee4ad524c367e8ba5dba6917 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 10:31:46 +0100 Subject: [PATCH 04/15] Fix autofill not working if load event not fired Signed-off-by: Emanuele Feliziani --- src/DeviceInterface/InterfacePrototype.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/DeviceInterface/InterfacePrototype.js b/src/DeviceInterface/InterfacePrototype.js index a1eef2975..30aed1a0a 100644 --- a/src/DeviceInterface/InterfacePrototype.js +++ b/src/DeviceInterface/InterfacePrototype.js @@ -65,6 +65,9 @@ class InterfacePrototype { /** @type {import("../../packages/device-api").DeviceApi} */ deviceApi; + /** @type {boolean} */ + alreadyInitialized; + /** * @param {GlobalConfig} config * @param {import("../../packages/device-api").DeviceApi} deviceApi @@ -78,6 +81,7 @@ class InterfacePrototype { this.scanner = createScanner(this, { initialDelay: this.initialSetupDelayMs }) + this.alreadyInitialized = false } /** @@ -236,6 +240,8 @@ class InterfacePrototype { } async startInit () { + if (this.alreadyInitialized) return + await this.refreshSettings() this.addDeviceListeners() @@ -256,6 +262,8 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { initFormSubmissionsApi(this.scanner.forms) } + + this.alreadyInitialized = true } /** @@ -294,12 +302,19 @@ class InterfacePrototype { async init () { const isEnabled = await this.isEnabled() if (!isEnabled) return + + const handler = async () => { + if (document.readyState === 'complete') { + await this.startInit() + window.removeEventListener('load', handler) + document.removeEventListener('readystatechange', handler) + } + } if (document.readyState === 'complete') { await this.startInit() } else { - window.addEventListener('load', () => { - this.startInit() - }) + window.addEventListener('load', handler) + document.addEventListener('readystatechange', handler) } } From d7cd42e4b91abfda4dec56e5925d2ddfb045bdf7 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 10:33:08 +0100 Subject: [PATCH 05/15] Improve accuracy Signed-off-by: Emanuele Feliziani --- src/Form/FormAnalyzer.js | 2 +- src/Form/matching-configuration.js | 4 ++-- src/Form/selectors-css.js | 17 +++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Form/FormAnalyzer.js b/src/Form/FormAnalyzer.js index c8c1d726f..ce82445f6 100644 --- a/src/Form/FormAnalyzer.js +++ b/src/Form/FormAnalyzer.js @@ -8,7 +8,7 @@ const signupRegex = new RegExp( /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat) password|password confirm?/i ) const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i) -const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i) +const strictSignupRegex = new RegExp(/sign.?up|join|register|(create|new).+account|enroll|settings|preferences|profile|update/i) class FormAnalyzer { /** @type HTMLElement */ diff --git a/src/Form/matching-configuration.js b/src/Form/matching-configuration.js index 99825bd77..7fe39b640 100644 --- a/src/Form/matching-configuration.js +++ b/src/Form/matching-configuration.js @@ -255,9 +255,9 @@ const matchingConfiguration = { /** @type {DDGMatcherConfiguration} */ ddgMatcher: { matchers: { - email: {match: '.mail\\b', skip: 'phone|name|reservation number', forceUnknown: 'search|filter|subject|title|\btab\b'}, + email: {match: '.mail\\b|apple.?id', skip: 'phone|name|reservation number|code', forceUnknown: 'search|filter|subject|title|\btab\b'}, password: {match: 'password', forceUnknown: 'captcha|mfa|2fa|two factor'}, - username: {match: '(user|account|apple|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', forceUnknown: 'search|policy'}, + username: {match: '(user|account|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', forceUnknown: 'search|policy'}, // CC cardName: {match: '(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)'}, diff --git a/src/Form/selectors-css.js b/src/Form/selectors-css.js index 067b67ee1..951f3e1fc 100644 --- a/src/Form/selectors-css.js +++ b/src/Form/selectors-css.js @@ -11,17 +11,16 @@ a[href="#"][id*=button i], a[href="#"][id*=btn i]` const email = ` -input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), +input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), -input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]), -input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), +input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), +input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), -input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[name=username][type=email], input[autocomplete=email]` @@ -158,21 +157,21 @@ const addressCountryCode = [ const birthdayDay = ` [name=bday-day], -[name=birthday_day], [name=birthday-day], +[name*=birthday_day i], [name*=birthday-day i], [name=date_of_birth_day], [name=date-of-birth-day], [name^=birthdate_d], [name^=birthdate-d], [aria-label="birthday" i][placeholder="day" i]` const birthdayMonth = ` [name=bday-month], -[name=birthday_month], [name=birthday-month], +[name*=birthday_month i], [name*=birthday-month i], [name=date_of_birth_month], [name=date-of-birth-month], [name^=birthdate_m], [name^=birthdate-m], select[name="mm"]` const birthdayYear = ` [name=bday-year], -[name=birthday_year], [name=birthday-year], +[name*=birthday_year i], [name*=birthday-year i], [name=date_of_birth_year], [name=date-of-birth-year], [name^=birthdate_y], [name^=birthdate-y], [aria-label="birthday" i][placeholder="year" i]` @@ -183,7 +182,9 @@ const username = [ // fix for `aa.com` `input[name="loginId" i]`, // fix for https://online.mbank.pl/pl/Login - `input[name="userID" i]`, + `input[name="userid" i]`, + `input[name="user_id" i]`, + `input[name="user-id" i]`, `input[id="login-id" i]`, `input[name=accountname i]`, `input[autocomplete=username]`, From cd4b7e74ff7bedd774de45e87968e54e93c389b7 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 10:39:55 +0100 Subject: [PATCH 06/15] Add a bunch of test cases Signed-off-by: Emanuele Feliziani --- src/Form/test-cases/airbnb_signup.html | 424 ++++++++++++++++++ .../cookpad_email-confirmation-code.html | 19 + src/Form/test-cases/index.js | 5 +- .../instagram_email-confirmation-code.html | 15 + 4 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 src/Form/test-cases/airbnb_signup.html create mode 100644 src/Form/test-cases/cookpad_email-confirmation-code.html create mode 100644 src/Form/test-cases/instagram_email-confirmation-code.html diff --git a/src/Form/test-cases/airbnb_signup.html b/src/Form/test-cases/airbnb_signup.html new file mode 100644 index 000000000..a58fa35aa --- /dev/null +++ b/src/Form/test-cases/airbnb_signup.html @@ -0,0 +1,424 @@ + +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
Make sure it matches the name on your government + ID. +
+
+
+
+
+
Birthday
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
We'll email you trip confirmations and receipts. +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+ Password strength: weak
+
+
+
+
+
+
+ + + +
+ Can't contain your name or email address +
+
+
+
+
+ + + +
+ At least 8 characters +
+
+
+
+
+ + + +
+ Contains a number or symbol +
+
+
+
+
+
+ Opt out of marketing from Airbnb using the check box after the button labeled Agree and Continue on this page +
By selecting Agree and continue, I agree to Airbnb’s Terms of Service, Payments + Terms of Service, and Nondiscrimination + Policy and acknowledge the Privacy + Policy. +
+
+ +
+
+
Airbnb will send you members-only deals, inspiration, marketing emails, and push notifications. + You can opt out of receiving these at any time in your account settings or directly from the marketing + notification. +
+
+
diff --git a/src/Form/test-cases/cookpad_email-confirmation-code.html b/src/Form/test-cases/cookpad_email-confirmation-code.html new file mode 100644 index 000000000..7dac5a22a --- /dev/null +++ b/src/Form/test-cases/cookpad_email-confirmation-code.html @@ -0,0 +1,19 @@ + +
+
+

Enter the verification code sent to your email

+
+ + +
+ + +

+ +

+
+ Get a new verification code + +
diff --git a/src/Form/test-cases/index.js b/src/Form/test-cases/index.js index 49f9146cd..f0e0a1eeb 100644 --- a/src/Form/test-cases/index.js +++ b/src/Form/test-cases/index.js @@ -256,5 +256,8 @@ export default [ { html: 'eventbrite_fake-signup.html' }, { html: 'ryanair_cc-card-name.html' }, { html: 'paypal_otp.html' }, - { html: 'postnews_login.html' } + { html: 'postnews_login.html' }, + { html: 'airbnb_signup.html' }, + { html: 'cookpad_email-confirmation-code.html', expectedFailures: ['unknown'] }, + { html: 'instagram_email-confirmation-code.html' } ] diff --git a/src/Form/test-cases/instagram_email-confirmation-code.html b/src/Form/test-cases/instagram_email-confirmation-code.html new file mode 100644 index 000000000..ec599090c --- /dev/null +++ b/src/Form/test-cases/instagram_email-confirmation-code.html @@ -0,0 +1,15 @@ + +
+
+
+
+ +
+
+ +
+
+
From 59ec0c19d0dc1c1e27df11dd365b67f59167adff Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 11:28:01 +0100 Subject: [PATCH 07/15] Improve UX on hybrid forms when we have identities Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 97 ++-- dist/autofill.js | 97 ++-- src/Form/Form.js | 4 +- src/Form/FormAnalyzer.js | 35 +- src/Form/inputTypeConfig.js | 4 +- src/Form/matching.js | 8 +- src/Form/matching.test.js | 35 +- .../Resources/assets/autofill-debug.js | 480 ++++++++++++------ swift-package/Resources/assets/autofill.js | 445 ++++++++++------ 9 files changed, 802 insertions(+), 403 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 48432d735..b558ad4bd 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -8341,6 +8341,8 @@ class InterfacePrototype { /** @type {import("../../packages/device-api").DeviceApi} */ + /** @type {boolean} */ + /** * @param {GlobalConfig} config * @param {import("../../packages/device-api").DeviceApi} deviceApi @@ -8375,6 +8377,8 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); + _defineProperty(this, "alreadyInitialized", void 0); + _classPrivateFieldInitSpec(this, _data2, { writable: true, value: { @@ -8392,6 +8396,7 @@ class InterfacePrototype { this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); + this.alreadyInitialized = false; } /** * Implementors should override this with a UI controller that suits @@ -8566,6 +8571,7 @@ class InterfacePrototype { } async startInit() { + if (this.alreadyInitialized) return; await this.refreshSettings(); this.addDeviceListeners(); await this.setupAutofill(); @@ -8582,6 +8588,8 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } + + this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -8623,12 +8631,19 @@ class InterfacePrototype { const isEnabled = await this.isEnabled(); if (!isEnabled) return; + const handler = async () => { + if (document.readyState === 'complete') { + await this.startInit(); + window.removeEventListener('load', handler); + document.removeEventListener('readystatechange', handler); + } + }; + if (document.readyState === 'complete') { await this.startInit(); } else { - window.addEventListener('load', () => { - this.startInit(); - }); + window.addEventListener('load', handler); + document.addEventListener('readystatechange', handler); } } /** @@ -9614,6 +9629,7 @@ class Form { this.formAnalyzer = new _FormAnalyzer.default(form, input, matching); this.isLogin = this.formAnalyzer.isLogin; this.isSignup = this.formAnalyzer.isSignup; + this.isHybrid = this.formAnalyzer.isHybrid; this.device = deviceInterface; /** @type Record<'all' | SupportedMainTypes, Set> */ @@ -9931,8 +9947,9 @@ class Form { this.inputs.all.add(input); const opts = { isLogin: this.isLogin, + isHybrid: this.isHybrid, hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), - isMobile: this.device.globalConfig.isMobileApp + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities }; this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); @@ -10197,7 +10214,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat) password|password confirm?/i); const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|(create|new).+account|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -10227,7 +10244,13 @@ class FormAnalyzer { * @type {string[]} */ - this.signals = []; // Avoid autofill on our signup page + this.signals = []; + /** + * A hybrid form can be either a login or a signup, the site uses a single form for both + * @type {boolean} + */ + + this.isHybrid = false; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { return this; @@ -10239,10 +10262,12 @@ class FormAnalyzer { } get isLogin() { + if (this.isHybrid) return false; return this.autofillSignal < 0; } get isSignup() { + if (this.isHybrid) return false; return this.autofillSignal >= 0; } /** @@ -10296,7 +10321,8 @@ class FormAnalyzer { const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { - this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); + this.signals.push("hybrid form: ".concat(signalType)); + this.isHybrid = true; return this; } @@ -10334,28 +10360,26 @@ class FormAnalyzer { this.updateSignal({ string: pageTitle, strength: 2, - signalType: "page title: ".concat(pageTitle) + signalType: "page title: ".concat(pageTitle), + shouldCheckUnifiedForm: true }); } evaluatePageHeadings() { const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [id*="title"]'); - - if (headings) { - headings.forEach(_ref2 => { - let { - textContent - } = _ref2; - textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: "heading: ".concat(textContent), - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }); + headings.forEach(_ref2 => { + let { + textContent + } = _ref2; + textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: "heading: ".concat(textContent), + shouldCheckUnifiedForm: true, + shouldBeConservative: true }); - } + }); } evaluatePage() { @@ -11581,11 +11605,12 @@ const inputTypeConfig = { shouldDecorate: async (input, _ref4) => { let { isLogin, + isHybrid, device } = _ref4; // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin) { + if (isLogin || isHybrid) { return canBeAutofilled(input, device); } // at this point, it's not a 'login' form, so we could offer to provide a password @@ -12005,8 +12030,8 @@ const matchingConfiguration = { ddgMatcher: { matchers: { email: { - match: '.mail\\b', - skip: 'phone|name|reservation number', + match: '.mail\\b|apple.?id', + skip: 'phone|name|reservation number|code', forceUnknown: 'search|filter|subject|title|\btab\b' }, password: { @@ -12014,7 +12039,7 @@ const matchingConfiguration = { forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { - match: '(user|account|apple|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', + match: '(user|account|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', forceUnknown: 'search|policy' }, // CC @@ -12714,8 +12739,9 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - if (opts.isLogin) { - if (!opts.isMobile && !opts.hasCredentials) { + if (opts.isLogin || opts.isHybrid) { + // Show identities when supported and there are no credentials + if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { return 'identities.emailAddress'; } @@ -12741,8 +12767,9 @@ class Matching { /** * @typedef {{ * isLogin?: boolean, + * isHybrid?: boolean, * hasCredentials?: boolean, - * isMobile?: boolean + * supportsIdentitiesAutofill?: boolean * }} SetInputTypeOpts */ @@ -13425,7 +13452,7 @@ const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not( exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; @@ -13447,12 +13474,12 @@ const addressProvince = "\n[name=province], [name=state], [autocomplete=address- const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name=birthday_day], [name=birthday-day],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name=birthday_month], [name=birthday-month],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name=birthday_year], [name=birthday-year],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; +const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userID\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/dist/autofill.js b/dist/autofill.js index 0c04317fa..f4228bed0 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -4665,6 +4665,8 @@ class InterfacePrototype { /** @type {import("../../packages/device-api").DeviceApi} */ + /** @type {boolean} */ + /** * @param {GlobalConfig} config * @param {import("../../packages/device-api").DeviceApi} deviceApi @@ -4699,6 +4701,8 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); + _defineProperty(this, "alreadyInitialized", void 0); + _classPrivateFieldInitSpec(this, _data2, { writable: true, value: { @@ -4716,6 +4720,7 @@ class InterfacePrototype { this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); + this.alreadyInitialized = false; } /** * Implementors should override this with a UI controller that suits @@ -4890,6 +4895,7 @@ class InterfacePrototype { } async startInit() { + if (this.alreadyInitialized) return; await this.refreshSettings(); this.addDeviceListeners(); await this.setupAutofill(); @@ -4906,6 +4912,8 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } + + this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -4947,12 +4955,19 @@ class InterfacePrototype { const isEnabled = await this.isEnabled(); if (!isEnabled) return; + const handler = async () => { + if (document.readyState === 'complete') { + await this.startInit(); + window.removeEventListener('load', handler); + document.removeEventListener('readystatechange', handler); + } + }; + if (document.readyState === 'complete') { await this.startInit(); } else { - window.addEventListener('load', () => { - this.startInit(); - }); + window.addEventListener('load', handler); + document.addEventListener('readystatechange', handler); } } /** @@ -5938,6 +5953,7 @@ class Form { this.formAnalyzer = new _FormAnalyzer.default(form, input, matching); this.isLogin = this.formAnalyzer.isLogin; this.isSignup = this.formAnalyzer.isSignup; + this.isHybrid = this.formAnalyzer.isHybrid; this.device = deviceInterface; /** @type Record<'all' | SupportedMainTypes, Set> */ @@ -6255,8 +6271,9 @@ class Form { this.inputs.all.add(input); const opts = { isLogin: this.isLogin, + isHybrid: this.isHybrid, hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), - isMobile: this.device.globalConfig.isMobileApp + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities }; this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); @@ -6521,7 +6538,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat) password|password confirm?/i); const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|(create|new).+account|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -6551,7 +6568,13 @@ class FormAnalyzer { * @type {string[]} */ - this.signals = []; // Avoid autofill on our signup page + this.signals = []; + /** + * A hybrid form can be either a login or a signup, the site uses a single form for both + * @type {boolean} + */ + + this.isHybrid = false; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { return this; @@ -6563,10 +6586,12 @@ class FormAnalyzer { } get isLogin() { + if (this.isHybrid) return false; return this.autofillSignal < 0; } get isSignup() { + if (this.isHybrid) return false; return this.autofillSignal >= 0; } /** @@ -6620,7 +6645,8 @@ class FormAnalyzer { const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { - this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); + this.signals.push("hybrid form: ".concat(signalType)); + this.isHybrid = true; return this; } @@ -6658,28 +6684,26 @@ class FormAnalyzer { this.updateSignal({ string: pageTitle, strength: 2, - signalType: "page title: ".concat(pageTitle) + signalType: "page title: ".concat(pageTitle), + shouldCheckUnifiedForm: true }); } evaluatePageHeadings() { const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [id*="title"]'); - - if (headings) { - headings.forEach(_ref2 => { - let { - textContent - } = _ref2; - textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: "heading: ".concat(textContent), - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }); + headings.forEach(_ref2 => { + let { + textContent + } = _ref2; + textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: "heading: ".concat(textContent), + shouldCheckUnifiedForm: true, + shouldBeConservative: true }); - } + }); } evaluatePage() { @@ -7905,11 +7929,12 @@ const inputTypeConfig = { shouldDecorate: async (input, _ref4) => { let { isLogin, + isHybrid, device } = _ref4; // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin) { + if (isLogin || isHybrid) { return canBeAutofilled(input, device); } // at this point, it's not a 'login' form, so we could offer to provide a password @@ -8329,8 +8354,8 @@ const matchingConfiguration = { ddgMatcher: { matchers: { email: { - match: '.mail\\b', - skip: 'phone|name|reservation number', + match: '.mail\\b|apple.?id', + skip: 'phone|name|reservation number|code', forceUnknown: 'search|filter|subject|title|\btab\b' }, password: { @@ -8338,7 +8363,7 @@ const matchingConfiguration = { forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { - match: '(user|account|apple|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', + match: '(user|account|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', forceUnknown: 'search|policy' }, // CC @@ -9038,8 +9063,9 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - if (opts.isLogin) { - if (!opts.isMobile && !opts.hasCredentials) { + if (opts.isLogin || opts.isHybrid) { + // Show identities when supported and there are no credentials + if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { return 'identities.emailAddress'; } @@ -9065,8 +9091,9 @@ class Matching { /** * @typedef {{ * isLogin?: boolean, + * isHybrid?: boolean, * hasCredentials?: boolean, - * isMobile?: boolean + * supportsIdentitiesAutofill?: boolean * }} SetInputTypeOpts */ @@ -9749,7 +9776,7 @@ const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not( exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; @@ -9771,12 +9798,12 @@ const addressProvince = "\n[name=province], [name=state], [autocomplete=address- const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name=birthday_day], [name=birthday-day],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name=birthday_month], [name=birthday-month],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name=birthday_year], [name=birthday-year],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; +const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userID\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/src/Form/Form.js b/src/Form/Form.js index 338f8529e..c4a425e77 100644 --- a/src/Form/Form.js +++ b/src/Form/Form.js @@ -47,6 +47,7 @@ class Form { this.formAnalyzer = new FormAnalyzer(form, input, matching) this.isLogin = this.formAnalyzer.isLogin this.isSignup = this.formAnalyzer.isSignup + this.isHybrid = this.formAnalyzer.isHybrid this.device = deviceInterface /** @type Record<'all' | SupportedMainTypes, Set> */ @@ -332,8 +333,9 @@ class Form { const opts = { isLogin: this.isLogin, + isHybrid: this.isHybrid, hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), - isMobile: this.device.globalConfig.isMobileApp + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities } this.matching.setInputType(input, this.form, opts) diff --git a/src/Form/FormAnalyzer.js b/src/Form/FormAnalyzer.js index ce82445f6..8dbc29de4 100644 --- a/src/Form/FormAnalyzer.js +++ b/src/Form/FormAnalyzer.js @@ -34,6 +34,12 @@ class FormAnalyzer { */ this.signals = [] + /** + * A hybrid form can be either a login or a signup, the site uses a single form for both + * @type {boolean} + */ + this.isHybrid = false + // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { return this @@ -45,10 +51,14 @@ class FormAnalyzer { } get isLogin () { + if (this.isHybrid) return false + return this.autofillSignal < 0 } get isSignup () { + if (this.isHybrid) return false + return this.autofillSignal >= 0 } @@ -99,7 +109,8 @@ class FormAnalyzer { // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { - this.decreaseSignalBy(strength + 2, `Unified detected ${signalType}`) + this.signals.push(`hybrid form: ${signalType}`) + this.isHybrid = true return this } @@ -133,23 +144,21 @@ class FormAnalyzer { evaluatePageTitle () { const pageTitle = document.title - this.updateSignal({string: pageTitle, strength: 2, signalType: `page title: ${pageTitle}`}) + this.updateSignal({string: pageTitle, strength: 2, signalType: `page title: ${pageTitle}`, shouldCheckUnifiedForm: true}) } evaluatePageHeadings () { const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [id*="title"]') - if (headings) { - headings.forEach(({textContent}) => { - textContent = removeExcessWhitespace(textContent || '') - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: `heading: ${textContent}`, - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }) + headings.forEach(({textContent}) => { + textContent = removeExcessWhitespace(textContent || '') + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: `heading: ${textContent}`, + shouldCheckUnifiedForm: true, + shouldBeConservative: true }) - } + }) } evaluatePage () { diff --git a/src/Form/inputTypeConfig.js b/src/Form/inputTypeConfig.js index 527b489fe..0c01d16b8 100644 --- a/src/Form/inputTypeConfig.js +++ b/src/Form/inputTypeConfig.js @@ -74,9 +74,9 @@ const inputTypeConfig = { } return '' }, - shouldDecorate: async (input, {isLogin, device}) => { + shouldDecorate: async (input, {isLogin, isHybrid, device}) => { // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin) { + if (isLogin || isHybrid) { return canBeAutofilled(input, device) } diff --git a/src/Form/matching.js b/src/Form/matching.js index 3b8ff41cb..940a53279 100644 --- a/src/Form/matching.js +++ b/src/Form/matching.js @@ -209,8 +209,9 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - if (opts.isLogin) { - if (!opts.isMobile && !opts.hasCredentials) { + if (opts.isLogin || opts.isHybrid) { + // Show identities when supported and there are no credentials + if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { return 'identities.emailAddress' } @@ -237,8 +238,9 @@ class Matching { /** * @typedef {{ * isLogin?: boolean, + * isHybrid?: boolean, * hasCredentials?: boolean, - * isMobile?: boolean + * supportsIdentitiesAutofill?: boolean * }} SetInputTypeOpts */ diff --git a/src/Form/matching.test.js b/src/Form/matching.test.js index fa0ebc147..2bd8657fe 100644 --- a/src/Form/matching.test.js +++ b/src/Form/matching.test.js @@ -114,14 +114,43 @@ describe('matching', () => { `, subtype: 'identities.firstName' + }, + { + // respects the options parameter + html: ``, + subtype: 'credentials.username', + opts: {isHybrid: true, hasCredentials: false} + }, + { + // respects the options parameter + html: ``, + subtype: 'identities.emailAddress', + opts: {isHybrid: true, hasCredentials: false, supportsIdentitiesAutofill: true} + }, + { + // respects the options parameter + html: ``, + subtype: 'identities.emailAddress', + opts: {isLogin: true, hasCredentials: false, supportsIdentitiesAutofill: true} + }, + { + // respects the options parameter + html: ``, + subtype: 'credentials.username', + opts: {isLogin: true, hasCredentials: true, supportsIdentitiesAutofill: true} + }, + { + // respects the options parameter + html: ``, + subtype: 'credentials.username', + opts: {isHybrid: true, hasCredentials: true} } - ])(`$html should be '$subtype'`, (args) => { - const { html, subtype } = args + const { html, subtype, opts } = args const { formElement, inputs } = setFormHtml(html) const matching = createMatching() - const inferred = matching.inferInputType(inputs[0], formElement) + const inferred = matching.inferInputType(inputs[0], formElement, opts) expect(inferred).toBe(subtype) }) it('should not continue past a ddg-matcher that has a "not" regex', () => { diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index c19a3c348..b558ad4bd 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -6586,6 +6586,9 @@ module.exports={ "claro.com.br": { "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" }, + "classmates.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" + }, "clien.net": { "password-rules": "minlength: 5; required: lower, upper; required: digit;" }, @@ -6892,15 +6895,15 @@ module.exports={ "medicare.gov": { "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" }, + "member.everbridge.net": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, "metlife.com": { "password-rules": "minlength: 6; maxlength: 20;" }, "microsoft.com": { "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" }, - "minecraft.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: ascii-printable;" - }, "mintmobile.com": { "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" }, @@ -6925,6 +6928,9 @@ module.exports={ "myhealthrecord.com": { "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" }, + "mysedgwick.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" + }, "mysubaru.com": { "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" }, @@ -7292,7 +7298,7 @@ function createDevice() { return new _ExtensionInterface.ExtensionInterface(globalConfig, deviceApi, settings); } -},{"../packages/device-api/index.js":14,"./DeviceInterface/AndroidInterface.js":23,"./DeviceInterface/AppleDeviceInterface.js":24,"./DeviceInterface/AppleOverlayDeviceInterface.js":25,"./DeviceInterface/ExtensionInterface.js":26,"./DeviceInterface/WindowsInterface.js":28,"./DeviceInterface/WindowsOverlayDeviceInterface.js":29,"./Settings.js":49,"./config.js":61,"./deviceApiCalls/transports/transports.js":69}],23:[function(require,module,exports){ +},{"../packages/device-api/index.js":14,"./DeviceInterface/AndroidInterface.js":23,"./DeviceInterface/AppleDeviceInterface.js":24,"./DeviceInterface/AppleOverlayDeviceInterface.js":25,"./DeviceInterface/ExtensionInterface.js":26,"./DeviceInterface/WindowsInterface.js":28,"./DeviceInterface/WindowsOverlayDeviceInterface.js":29,"./Settings.js":49,"./config.js":62,"./deviceApiCalls/transports/transports.js":70}],23:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -7439,7 +7445,7 @@ class AndroidInterface extends _InterfacePrototype.default { exports.AndroidInterface = AndroidInterface; -},{"../UI/controllers/NativeUIController.js":54,"../autofill-utils.js":59,"./InterfacePrototype.js":27,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],24:[function(require,module,exports){ +},{"../UI/controllers/NativeUIController.js":55,"../autofill-utils.js":60,"./InterfacePrototype.js":27,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],24:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -7896,7 +7902,7 @@ class AppleDeviceInterface extends _InterfacePrototype.default { exports.AppleDeviceInterface = AppleDeviceInterface; -},{"../../packages/device-api/index.js":14,"../Form/matching.js":41,"../UI/HTMLTooltip.js":52,"../UI/controllers/HTMLTooltipUIController.js":53,"../UI/controllers/NativeUIController.js":54,"../UI/controllers/OverlayUIController.js":55,"../autofill-utils.js":59,"../deviceApiCalls/__generated__/deviceApiCalls.js":63,"../deviceApiCalls/additionalDeviceApiCalls.js":65,"./InterfacePrototype.js":27,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],25:[function(require,module,exports){ +},{"../../packages/device-api/index.js":14,"../Form/matching.js":41,"../UI/HTMLTooltip.js":53,"../UI/controllers/HTMLTooltipUIController.js":54,"../UI/controllers/NativeUIController.js":55,"../UI/controllers/OverlayUIController.js":56,"../autofill-utils.js":60,"../deviceApiCalls/__generated__/deviceApiCalls.js":64,"../deviceApiCalls/additionalDeviceApiCalls.js":66,"./InterfacePrototype.js":27,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],25:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -7969,10 +7975,13 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter if (signedIn) { await this.getAddresses(); - } // setup overlay API pieces - + } + } + async postInit() { + // setup overlay API pieces this.overlay.showImmediately(); + super.postInit(); } /** * In the top-frame scenario we override the base 'selectedDetail'. @@ -7995,6 +8004,8 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter } providerStatusUpdated(data) { + var _this$uiController; + const { credentials, availableInputTypes @@ -8003,7 +8014,7 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter this.settings.setAvailableInputTypes(availableInputTypes); this.storeLocalCredentials(credentials); // rerender the tooltip - this.uiController.updateItems(credentials); + (_this$uiController = this.uiController) === null || _this$uiController === void 0 ? void 0 : _this$uiController.updateItems(credentials); } firePixel(pixelName) { @@ -8016,7 +8027,7 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter exports.AppleOverlayDeviceInterface = AppleOverlayDeviceInterface; -},{"../../packages/device-api/index.js":14,"../UI/controllers/HTMLTooltipUIController.js":53,"../deviceApiCalls/__generated__/deviceApiCalls.js":63,"../deviceApiCalls/__generated__/validators.zod.js":64,"./AppleDeviceInterface.js":24,"./overlayApi.js":31}],26:[function(require,module,exports){ +},{"../../packages/device-api/index.js":14,"../UI/controllers/HTMLTooltipUIController.js":54,"../deviceApiCalls/__generated__/deviceApiCalls.js":64,"../deviceApiCalls/__generated__/validators.zod.js":65,"./AppleDeviceInterface.js":24,"./overlayApi.js":31}],26:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -8032,8 +8043,15 @@ var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIControlle var _HTMLTooltip = require("../UI/HTMLTooltip.js"); +var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const POPUP_TYPES = { + EmailProtection: 'EmailProtection', + EmailSignup: 'EmailSignup' +}; + class ExtensionInterface extends _InterfacePrototype.default { /** * @override @@ -8044,12 +8062,34 @@ class ExtensionInterface extends _InterfacePrototype.default { css: ""), testMode: this.isTestMode() }; + const tooltipKinds = { + [POPUP_TYPES.EmailProtection]: 'legacy', + [POPUP_TYPES.EmailSignup]: 'emailsignup' + }; + const tooltipKind = tooltipKinds[this.getShowingTooltip()] || tooltipKinds[POPUP_TYPES.EmailProtection]; return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: 'legacy', + tooltipKind, device: this }, htmlTooltipOptions); } + get hasDismissedEmailSignup() { + // TODO -- implement peristed dismissed timestamp + return false; + } + + getShowingTooltip() { + if (this.hasLocalAddresses) { + return POPUP_TYPES.EmailProtection; + } + + if (this.settings.featureToggles.emailProtection_incontext_signup && !this.hasDismissedEmailSignup) { + return POPUP_TYPES.EmailSignup; + } + + return null; + } + async isEnabled() { return new Promise(resolve => { chrome.runtime.sendMessage({ @@ -8072,9 +8112,24 @@ class ExtensionInterface extends _InterfacePrototype.default { } postInit() { - if (this.hasLocalAddresses) { - const cleanup = this.scanner.init(); - this.addLogoutListener(cleanup); + switch (this.getShowingTooltip()) { + case POPUP_TYPES.EmailProtection: + { + const cleanup = this.scanner.init(); + this.addLogoutListener(cleanup); + break; + } + + case POPUP_TYPES.EmailSignup: + { + this.scanner.init(); + break; + } + + default: + { + break; + } } } @@ -8086,6 +8141,12 @@ class ExtensionInterface extends _InterfacePrototype.default { return resolve(data); })); } + + firePixel(pixelName) { + this.deviceApi.notify(new _deviceApiCalls.SendJSPixelCall({ + pixelName + })); + } /** * Used by the email web app * Settings page displays data of the logged in user data @@ -8196,7 +8257,7 @@ class ExtensionInterface extends _InterfacePrototype.default { exports.ExtensionInterface = ExtensionInterface; -},{"../UI/HTMLTooltip.js":52,"../UI/controllers/HTMLTooltipUIController.js":53,"../autofill-utils.js":59,"./InterfacePrototype.js":27}],27:[function(require,module,exports){ +},{"../UI/HTMLTooltip.js":53,"../UI/controllers/HTMLTooltipUIController.js":54,"../autofill-utils.js":60,"../deviceApiCalls/__generated__/deviceApiCalls.js":64,"./InterfacePrototype.js":27}],27:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -8276,10 +8337,12 @@ class InterfacePrototype { /** @type {import('../Scanner').Scanner} */ - /** @type {import("../UI/controllers/UIController.js").UIController} */ + /** @type {import("../UI/controllers/UIController.js").UIController | null} */ /** @type {import("../../packages/device-api").DeviceApi} */ + /** @type {boolean} */ + /** * @param {GlobalConfig} config * @param {import("../../packages/device-api").DeviceApi} deviceApi @@ -8314,6 +8377,8 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); + _defineProperty(this, "alreadyInitialized", void 0); + _classPrivateFieldInitSpec(this, _data2, { writable: true, value: { @@ -8326,11 +8391,12 @@ class InterfacePrototype { this.globalConfig = config; this.deviceApi = deviceApi; - this.uiController = this.createUIController(); this.settings = settings; + this.uiController = null; this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); + this.alreadyInitialized = false; } /** * Implementors should override this with a UI controller that suits @@ -8505,9 +8571,11 @@ class InterfacePrototype { } async startInit() { + if (this.alreadyInitialized) return; await this.refreshSettings(); this.addDeviceListeners(); - await this.setupAutofill(); // this is the temporary measure to support windows whilst we still have 'setupAutofill' + await this.setupAutofill(); + this.uiController = this.createUIController(); // this is the temporary measure to support windows whilst we still have 'setupAutofill' // eventually all interfaces will use this if (!this.isEnabledViaSettings()) { @@ -8520,6 +8588,8 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } + + this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -8561,12 +8631,19 @@ class InterfacePrototype { const isEnabled = await this.isEnabled(); if (!isEnabled) return; + const handler = async () => { + if (document.readyState === 'complete') { + await this.startInit(); + window.removeEventListener('load', handler); + document.removeEventListener('readystatechange', handler); + } + }; + if (document.readyState === 'complete') { - this.startInit(); + await this.startInit(); } else { - window.addEventListener('load', () => { - this.startInit(); - }); + window.addEventListener('load', handler); + document.addEventListener('readystatechange', handler); } } /** @@ -8653,6 +8730,8 @@ class InterfacePrototype { attachTooltip(form, input, click) { + var _this$uiController; + let trigger = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'userInitiated'; // Avoid flashing tooltip from background tabs on macOS if (document.visibilityState !== 'visible') return; // Only autoprompt on mobile devices @@ -8689,7 +8768,7 @@ class InterfacePrototype { // for example, generated passwords may get appended here const processedTopContext = this.preAttachTooltip(topContextData, input, form); - this.uiController.attach({ + (_this$uiController = this.uiController) === null || _this$uiController === void 0 ? void 0 : _this$uiController.attach({ input, form, click, @@ -8772,15 +8851,15 @@ class InterfacePrototype { } isTooltipActive() { - var _this$uiController$is, _this$uiController$is2, _this$uiController; + var _this$uiController$is, _this$uiController2, _this$uiController2$i; - return (_this$uiController$is = (_this$uiController$is2 = (_this$uiController = this.uiController).isActive) === null || _this$uiController$is2 === void 0 ? void 0 : _this$uiController$is2.call(_this$uiController)) !== null && _this$uiController$is !== void 0 ? _this$uiController$is : false; + return (_this$uiController$is = (_this$uiController2 = this.uiController) === null || _this$uiController2 === void 0 ? void 0 : (_this$uiController2$i = _this$uiController2.isActive) === null || _this$uiController2$i === void 0 ? void 0 : _this$uiController2$i.call(_this$uiController2)) !== null && _this$uiController$is !== void 0 ? _this$uiController$is : false; } removeTooltip() { - var _this$uiController$re, _this$uiController2; + var _this$uiController3, _this$uiController3$r; - return (_this$uiController$re = (_this$uiController2 = this.uiController).removeTooltip) === null || _this$uiController$re === void 0 ? void 0 : _this$uiController$re.call(_this$uiController2, 'interface'); + return (_this$uiController3 = this.uiController) === null || _this$uiController3 === void 0 ? void 0 : (_this$uiController3$r = _this$uiController3.removeTooltip) === null || _this$uiController3$r === void 0 ? void 0 : _this$uiController3$r.call(_this$uiController3, 'interface'); } async setupSettingsPage() { @@ -8887,7 +8966,7 @@ class InterfacePrototype { providerStatusUpdated(data) { try { - var _availableInputTypes$; + var _this$uiController4, _availableInputTypes$; const { credentials, @@ -8897,7 +8976,7 @@ class InterfacePrototype { this.settings.setAvailableInputTypes(availableInputTypes); this.storeLocalCredentials(credentials); // rerender the tooltip - this.uiController.updateItems(credentials); // If the tooltip is open on an autofill type that's not available, close it + (_this$uiController4 = this.uiController) === null || _this$uiController4 === void 0 ? void 0 : _this$uiController4.updateItems(credentials); // If the tooltip is open on an autofill type that's not available, close it const currentInputSubtype = (0, _matching.getSubtypeFromType)(this.getCurrentInputType()); @@ -9074,7 +9153,7 @@ class InterfacePrototype { var _default = InterfacePrototype; exports.default = _default; -},{"../../packages/device-api/index.js":14,"../Form/formatters.js":35,"../Form/matching.js":41,"../InputTypes/Credentials.js":44,"../PasswordGenerator.js":47,"../Scanner.js":48,"../Settings.js":49,"../UI/controllers/NativeUIController.js":54,"../autofill-utils.js":59,"../config.js":61,"../deviceApiCalls/__generated__/deviceApiCalls.js":63,"../deviceApiCalls/__generated__/validators.zod.js":64,"../deviceApiCalls/transports/transports.js":69,"./initFormSubmissionsApi.js":30}],28:[function(require,module,exports){ +},{"../../packages/device-api/index.js":14,"../Form/formatters.js":35,"../Form/matching.js":41,"../InputTypes/Credentials.js":44,"../PasswordGenerator.js":47,"../Scanner.js":48,"../Settings.js":49,"../UI/controllers/NativeUIController.js":55,"../autofill-utils.js":60,"../config.js":62,"../deviceApiCalls/__generated__/deviceApiCalls.js":64,"../deviceApiCalls/__generated__/validators.zod.js":65,"../deviceApiCalls/transports/transports.js":70,"./initFormSubmissionsApi.js":30}],28:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -9213,7 +9292,7 @@ class WindowsInterface extends _InterfacePrototype.default { exports.WindowsInterface = WindowsInterface; -},{"../UI/controllers/OverlayUIController.js":55,"../deviceApiCalls/__generated__/deviceApiCalls.js":63,"./InterfacePrototype.js":27}],29:[function(require,module,exports){ +},{"../UI/controllers/OverlayUIController.js":56,"../deviceApiCalls/__generated__/deviceApiCalls.js":64,"./InterfacePrototype.js":27}],29:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -9286,9 +9365,13 @@ class WindowsOverlayDeviceInterface extends _InterfacePrototype.default { async setupAutofill() { const response = await this.deviceApi.request(new _deviceApiCalls.GetAutofillInitDataCall(null)); // @ts-ignore - this.storeLocalData(response); // setup overlay API pieces + this.storeLocalData(response); + } + async postInit() { + // setup overlay API pieces this.overlay.showImmediately(); + super.postInit(); } /** * In the top-frame scenario, we send a message to the native @@ -9309,7 +9392,7 @@ class WindowsOverlayDeviceInterface extends _InterfacePrototype.default { exports.WindowsOverlayDeviceInterface = WindowsOverlayDeviceInterface; -},{"../UI/controllers/HTMLTooltipUIController.js":53,"../deviceApiCalls/__generated__/deviceApiCalls.js":63,"./InterfacePrototype.js":27,"./overlayApi.js":31}],30:[function(require,module,exports){ +},{"../UI/controllers/HTMLTooltipUIController.js":54,"../deviceApiCalls/__generated__/deviceApiCalls.js":64,"./InterfacePrototype.js":27,"./overlayApi.js":31}],30:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -9403,7 +9486,7 @@ function initFormSubmissionsApi(forms) { }); } -},{"../Form/matching.js":41,"../Form/selectors-css.js":42,"../autofill-utils.js":59}],31:[function(require,module,exports){ +},{"../Form/matching.js":41,"../Form/selectors-css.js":42,"../autofill-utils.js":60}],31:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -9427,9 +9510,9 @@ function overlayApi(device) { * page load every time it's opened. */ window.addEventListener('mouseMove', event => { - var _device$uiController$, _device$uiController; + var _device$uiController, _device$uiController$; - const activeTooltip = (_device$uiController$ = (_device$uiController = device.uiController).getActiveTooltip) === null || _device$uiController$ === void 0 ? void 0 : _device$uiController$.call(_device$uiController); + const activeTooltip = (_device$uiController = device.uiController) === null || _device$uiController === void 0 ? void 0 : (_device$uiController$ = _device$uiController.getActiveTooltip) === null || _device$uiController$ === void 0 ? void 0 : _device$uiController$.call(_device$uiController); activeTooltip === null || activeTooltip === void 0 ? void 0 : activeTooltip.focus(event.detail.x, event.detail.y); }); return { @@ -9437,7 +9520,7 @@ function overlayApi(device) { * When we are inside an 'overlay' - the HTML tooltip will be opened immediately */ showImmediately() { - var _device$uiController$2, _device$uiController2; + var _device$uiController2, _device$uiController3; const topContextData = device.getTopContextData(); if (!topContextData) throw new Error('unreachable, topContextData should be available'); // Provide dummy values @@ -9452,12 +9535,12 @@ function overlayApi(device) { }; // Create the tooltip, and set it as active - const tooltip = (_device$uiController$2 = (_device$uiController2 = device.uiController).createTooltip) === null || _device$uiController$2 === void 0 ? void 0 : _device$uiController$2.call(_device$uiController2, getPosition, topContextData); + const tooltip = (_device$uiController2 = device.uiController) === null || _device$uiController2 === void 0 ? void 0 : (_device$uiController3 = _device$uiController2.createTooltip) === null || _device$uiController3 === void 0 ? void 0 : _device$uiController3.call(_device$uiController2, getPosition, topContextData); if (tooltip) { - var _device$uiController$3, _device$uiController3; + var _device$uiController4, _device$uiController5; - (_device$uiController$3 = (_device$uiController3 = device.uiController).setActiveTooltip) === null || _device$uiController$3 === void 0 ? void 0 : _device$uiController$3.call(_device$uiController3, tooltip); + (_device$uiController4 = device.uiController) === null || _device$uiController4 === void 0 ? void 0 : (_device$uiController5 = _device$uiController4.setActiveTooltip) === null || _device$uiController5 === void 0 ? void 0 : _device$uiController5.call(_device$uiController4, tooltip); } }, @@ -9483,7 +9566,7 @@ function overlayApi(device) { }; } -},{"../deviceApiCalls/__generated__/deviceApiCalls.js":63}],32:[function(require,module,exports){ +},{"../deviceApiCalls/__generated__/deviceApiCalls.js":64}],32:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -9546,6 +9629,7 @@ class Form { this.formAnalyzer = new _FormAnalyzer.default(form, input, matching); this.isLogin = this.formAnalyzer.isLogin; this.isSignup = this.formAnalyzer.isSignup; + this.isHybrid = this.formAnalyzer.isHybrid; this.device = deviceInterface; /** @type Record<'all' | SupportedMainTypes, Set> */ @@ -9597,24 +9681,6 @@ class Form { /** @type HTMLElement */ e === null || e === void 0 ? void 0 : e.target); } - /** - * Checks that the form element doesn't contain an invalid field - * @return {boolean} - */ - - - isValid() { - if (this.form instanceof HTMLFormElement) { - return this.form.checkValidity(); - } // If the container is not a valid form, we must check fields individually - - - let validity = true; - this.execOnInputs(input => { - if (input.validity && !input.validity.valid) validity = false; - }, 'all', false); - return validity; - } submitHandler() { var _this$device$postSubm, _this$device; @@ -9626,7 +9692,6 @@ class Form { } if (this.handlerExecuted) return; - if (!this.isValid()) return; const values = this.getValues(); (_this$device$postSubm = (_this$device = this.device).postSubmit) === null || _this$device$postSubm === void 0 ? void 0 : _this$device$postSubm.call(_this$device, values, this); // mark this form as being handled @@ -9815,7 +9880,12 @@ class Form { categorizeInputs() { const selector = this.matching.cssSelector('FORM_INPUTS_SELECTOR'); - this.form.querySelectorAll(selector).forEach(input => this.addInput(input)); + + if (this.form.matches(selector)) { + this.addInput(this.form); + } else { + this.form.querySelectorAll(selector).forEach(input => this.addInput(input)); + } } get submitButtons() { @@ -9828,8 +9898,7 @@ class Form { attemptSubmissionIfNeeded() { if (!this.isLogin || // Only submit login forms - this.submitButtons.length > 1 || // Do not submit if we're unsure about the submit button - !this.isValid() // Do not submit invalid forms + this.submitButtons.length > 1 // Do not submit if we're unsure about the submit button ) return; // check for visible empty fields before attemtping submission // this is to avoid loops where a captcha keeps failing for the user @@ -9878,8 +9947,9 @@ class Form { this.inputs.all.add(input); const opts = { isLogin: this.isLogin, + isHybrid: this.isHybrid, hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), - isMobile: this.device.globalConfig.isMobileApp + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities }; this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); @@ -10123,7 +10193,7 @@ class Form { exports.Form = Form; -},{"../autofill-utils.js":59,"../constants.js":62,"./FormAnalyzer.js":33,"./formatters.js":35,"./inputStyles.js":36,"./inputTypeConfig.js":37,"./matching.js":41}],33:[function(require,module,exports){ +},{"../autofill-utils.js":60,"../constants.js":63,"./FormAnalyzer.js":33,"./formatters.js":35,"./inputStyles.js":36,"./inputTypeConfig.js":37,"./matching.js":41}],33:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -10142,9 +10212,9 @@ var _autofillUtils = require("../autofill-utils.js"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); -const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); +const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat) password|password confirm?/i); const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|(create|new).+account|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -10174,7 +10244,13 @@ class FormAnalyzer { * @type {string[]} */ - this.signals = []; // Avoid autofill on our signup page + this.signals = []; + /** + * A hybrid form can be either a login or a signup, the site uses a single form for both + * @type {boolean} + */ + + this.isHybrid = false; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { return this; @@ -10186,10 +10262,12 @@ class FormAnalyzer { } get isLogin() { + if (this.isHybrid) return false; return this.autofillSignal < 0; } get isSignup() { + if (this.isHybrid) return false; return this.autofillSignal >= 0; } /** @@ -10243,7 +10321,8 @@ class FormAnalyzer { const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { - this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); + this.signals.push("hybrid form: ".concat(signalType)); + this.isHybrid = true; return this; } @@ -10281,28 +10360,26 @@ class FormAnalyzer { this.updateSignal({ string: pageTitle, strength: 2, - signalType: "page title: ".concat(pageTitle) + signalType: "page title: ".concat(pageTitle), + shouldCheckUnifiedForm: true }); } evaluatePageHeadings() { const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [id*="title"]'); - - if (headings) { - headings.forEach(_ref2 => { - let { - textContent - } = _ref2; - textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: "heading: ".concat(textContent), - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }); + headings.forEach(_ref2 => { + let { + textContent + } = _ref2; + textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: "heading: ".concat(textContent), + shouldCheckUnifiedForm: true, + shouldBeConservative: true }); - } + }); } evaluatePage() { @@ -10335,8 +10412,19 @@ class FormAnalyzer { if (el.matches(this.matching.cssSelector('SUBMIT_BUTTON_SELECTOR'))) { - // If we're confident this is a submit button, it's a stronger signal - const strength = (0, _autofillUtils.isLikelyASubmitButton)(el) ? 20 : 2; + // If we're confident this is the submit button, it's a stronger signal + let likelyASubmit = (0, _autofillUtils.isLikelyASubmitButton)(el); + + if (likelyASubmit) { + this.form.querySelectorAll('input[type=submit], button[type=submit]').forEach(submit => { + // If there is another element marked as submit and this is not, flip back to false + if (el.type !== 'submit' && el !== submit) { + likelyASubmit = false; + } + }); + } + + const strength = likelyASubmit ? 20 : 2; this.updateSignal({ string, strength, @@ -10408,7 +10496,7 @@ class FormAnalyzer { var _default = FormAnalyzer; exports.default = _default; -},{"../autofill-utils.js":59,"../constants.js":62,"./matching-configuration.js":40,"./matching.js":41}],34:[function(require,module,exports){ +},{"../autofill-utils.js":60,"../constants.js":63,"./matching-configuration.js":40,"./matching.js":41}],34:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -11517,11 +11605,12 @@ const inputTypeConfig = { shouldDecorate: async (input, _ref4) => { let { isLogin, + isHybrid, device } = _ref4; // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin) { + if (isLogin || isHybrid) { return canBeAutofilled(input, device); } // at this point, it's not a 'login' form, so we could offer to provide a password @@ -11620,7 +11709,7 @@ const isFieldDecorated = input => { exports.isFieldDecorated = isFieldDecorated; -},{"../InputTypes/Credentials.js":44,"../InputTypes/CreditCard.js":45,"../InputTypes/Identity.js":46,"../UI/img/ddgPasswordIcon.js":57,"../constants.js":62,"./logo-svg.js":39,"./matching.js":41}],38:[function(require,module,exports){ +},{"../InputTypes/Credentials.js":44,"../InputTypes/CreditCard.js":45,"../InputTypes/Identity.js":46,"../UI/img/ddgPasswordIcon.js":58,"../constants.js":63,"./logo-svg.js":39,"./matching.js":41}],38:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -11941,8 +12030,8 @@ const matchingConfiguration = { ddgMatcher: { matchers: { email: { - match: '.mail\\b', - skip: 'phone|name|reservation number', + match: '.mail\\b|apple.?id', + skip: 'phone|name|reservation number|code', forceUnknown: 'search|filter|subject|title|\btab\b' }, password: { @@ -11950,7 +12039,7 @@ const matchingConfiguration = { forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { - match: '(user|account|apple|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', + match: '(user|account|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', forceUnknown: 'search|policy' }, // CC @@ -12650,8 +12739,9 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - if (opts.isLogin) { - if (!opts.isMobile && !opts.hasCredentials) { + if (opts.isLogin || opts.isHybrid) { + // Show identities when supported and there are no credentials + if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { return 'identities.emailAddress'; } @@ -12677,8 +12767,9 @@ class Matching { /** * @typedef {{ * isLogin?: boolean, + * isHybrid?: boolean, * hasCredentials?: boolean, - * isMobile?: boolean + * supportsIdentitiesAutofill?: boolean * }} SetInputTypeOpts */ @@ -13009,7 +13100,7 @@ class Matching { return false; } - const hasCCSelectorChild = formEl.querySelector(ccFieldSelector); // If the form contains one of the specific selectors, we have high confidence + const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); // If the form contains one of the specific selectors, we have high confidence if (hasCCSelectorChild) return true; // Read form attributes to find a signal @@ -13350,23 +13441,23 @@ function createMatching() { return new Matching(_matchingConfiguration.matchingConfiguration); } -},{"../constants.js":62,"./label-util.js":38,"./matching-configuration.js":40,"./selectors-css.js":42,"./vendor-regex.js":43}],42:[function(require,module,exports){ +},{"../constants.js":63,"./label-util.js":38,"./matching-configuration.js":40,"./selectors-css.js":42,"./vendor-regex.js":43}],42:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.__secret_do_not_use = exports.SUBMIT_BUTTON_SELECTOR = exports.FORM_INPUTS_SELECTOR = void 0; -const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=search]):not([name^=fake i]):not([data-description^=dummy i]),\nselect"; +const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=search]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]),\nselect"; exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; -const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; +const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i]"; const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i]"; @@ -13383,12 +13474,12 @@ const addressProvince = "\n[name=province], [name=state], [autocomplete=address- const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name=birthday_day], [name=birthday-day],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name=birthday_month], [name=birthday-month],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name=birthday_year], [name=birthday-year],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; +const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userID\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later @@ -14060,7 +14151,7 @@ class DefaultScanner { let element = input; // traverse the DOM to search for related inputs - while (element.parentElement && element.parentElement !== document.body) { + while (element.parentElement && element.parentElement !== document.documentElement) { var _element$parentElemen; // If parent includes a form return the current element to avoid overlapping forms @@ -14180,7 +14271,7 @@ function createScanner(device, scannerOptions) { }); } -},{"./Form/Form.js":32,"./Form/matching.js":41,"./Form/selectors-css.js":42,"./autofill-utils.js":59}],49:[function(require,module,exports){ +},{"./Form/Form.js":32,"./Form/matching.js":41,"./Form/selectors-css.js":42,"./autofill-utils.js":60}],49:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -14473,6 +14564,7 @@ _defineProperty(Settings, "defaults", { credentials_saving: false, password_generation: false, emailProtection: false, + emailProtection_incontext_signup: false, inputType_identities: false, inputType_credentials: false, inputType_creditCards: false, @@ -14515,7 +14607,7 @@ _defineProperty(Settings, "defaults", { enabled: null }); -},{"../packages/device-api/index.js":14,"./autofill-utils.js":59,"./deviceApiCalls/__generated__/deviceApiCalls.js":63,"./deviceApiCalls/__generated__/validators.zod.js":64,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],50:[function(require,module,exports){ +},{"../packages/device-api/index.js":14,"./autofill-utils.js":60,"./deviceApiCalls/__generated__/deviceApiCalls.js":64,"./deviceApiCalls/__generated__/validators.zod.js":65,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],50:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -14577,7 +14669,7 @@ class DataHTMLTooltip extends _HTMLTooltip.default { var _default = DataHTMLTooltip; exports.default = _default; -},{"../autofill-utils.js":59,"./HTMLTooltip.js":52}],51:[function(require,module,exports){ +},{"../autofill-utils.js":60,"./HTMLTooltip.js":53}],51:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -14612,11 +14704,14 @@ class EmailHTMLTooltip extends _HTMLTooltip.default { } }; + const firePixel = this.device.firePixel.bind(this.device); this.registerClickableButton(this.usePersonalButton, () => { this.fillForm('personalAddress'); + firePixel('autofill_personal_address'); }); this.registerClickableButton(this.usePrivateButton, () => { this.fillForm('privateAddress'); + firePixel('autofill_private_address'); }); // Get the alias from the extension this.device.getAddresses().then(this.updateAddresses); @@ -14644,7 +14739,39 @@ class EmailHTMLTooltip extends _HTMLTooltip.default { var _default = EmailHTMLTooltip; exports.default = _default; -},{"../autofill-utils.js":59,"./HTMLTooltip.js":52}],52:[function(require,module,exports){ +},{"../autofill-utils.js":60,"./HTMLTooltip.js":53}],52:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _HTMLTooltip = _interopRequireDefault(require("./HTMLTooltip.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +class EmailSignupHTMLTooltip extends _HTMLTooltip.default { + /** + * @param {import("../DeviceInterface/InterfacePrototype").default} device + */ + render(device) { + this.device = device; + this.shadow.innerHTML = "\n".concat(this.options.css, "\n
\n \n
"); + this.tooltip = this.shadow.querySelector('.tooltip'); + this.dismissEmailSignup = this.shadow.querySelector('.js-dismiss-email-signup'); + this.registerClickableButton(this.dismissEmailSignup, () => {// TODO: Persist dismissal + }); + this.init(); + return this; + } + +} + +var _default = EmailSignupHTMLTooltip; +exports.default = _default; + +},{"./HTMLTooltip.js":53}],53:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -14928,7 +15055,7 @@ exports.HTMLTooltip = HTMLTooltip; var _default = HTMLTooltip; exports.default = _default; -},{"../Form/matching.js":41,"../autofill-utils.js":59,"./styles/styles.js":58}],53:[function(require,module,exports){ +},{"../Form/matching.js":41,"../autofill-utils.js":60,"./styles/styles.js":59}],54:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -14942,6 +15069,8 @@ var _DataHTMLTooltip = _interopRequireDefault(require("../DataHTMLTooltip.js")); var _EmailHTMLTooltip = _interopRequireDefault(require("../EmailHTMLTooltip.js")); +var _EmailSignupHTMLTooltop = _interopRequireDefault(require("../EmailSignupHTMLTooltop.js")); + var _HTMLTooltip = require("../HTMLTooltip.js"); var _UIController = require("./UIController.js"); @@ -14952,7 +15081,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope /** * @typedef HTMLTooltipControllerOptions - * @property {"modern" | "legacy"} tooltipKind - A choice between the newer Autofill UI vs the older one used in the extension + * @property {"modern" | "legacy" | "emailsignup"} tooltipKind - A choice between the newer Autofill UI vs the older ones used in the extension * @property {import("../../DeviceInterface/InterfacePrototype").default} device - The device interface that's currently running * regardless of whether this Controller has an open tooltip, or not */ @@ -15031,6 +15160,10 @@ class HTMLTooltipUIController extends _UIController.UIController { if (this._options.tooltipKind === 'legacy') { return new _EmailHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + } + + if (this._options.tooltipKind === 'emailsignup') { + return new _EmailSignupHTMLTooltop.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); } // collect the data for each item to display @@ -15192,7 +15325,7 @@ class HTMLTooltipUIController extends _UIController.UIController { exports.HTMLTooltipUIController = HTMLTooltipUIController; -},{"../../Form/inputTypeConfig.js":37,"../DataHTMLTooltip.js":50,"../EmailHTMLTooltip.js":51,"../HTMLTooltip.js":52,"./UIController.js":56}],54:[function(require,module,exports){ +},{"../../Form/inputTypeConfig.js":37,"../DataHTMLTooltip.js":50,"../EmailHTMLTooltip.js":51,"../EmailSignupHTMLTooltop.js":52,"../HTMLTooltip.js":53,"./UIController.js":57}],55:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -15292,7 +15425,7 @@ class NativeUIController extends _UIController.UIController { exports.NativeUIController = NativeUIController; -},{"../../Form/matching.js":41,"../../deviceApiCalls/__generated__/deviceApiCalls.js":63,"./UIController.js":56}],55:[function(require,module,exports){ +},{"../../Form/matching.js":41,"../../deviceApiCalls/__generated__/deviceApiCalls.js":64,"./UIController.js":57}],56:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -15575,7 +15708,7 @@ class OverlayUIController extends _UIController.UIController { exports.OverlayUIController = OverlayUIController; -},{"../../Form/matching.js":41,"./UIController.js":56}],56:[function(require,module,exports){ +},{"../../Form/matching.js":41,"./UIController.js":57}],57:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -15668,7 +15801,7 @@ class UIController { exports.UIController = UIController; -},{}],57:[function(require,module,exports){ +},{}],58:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -15690,17 +15823,17 @@ exports.ddgCcIconFilled = ddgCcIconFilled; const ddgIdentityIconBase = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4KPHBhdGggeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJub25lIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMiAyMWMyLjE0MyAwIDQuMTExLS43NSA1LjY1Ny0yLS42MjYtLjUwNi0xLjMxOC0uOTI3LTIuMDYtMS4yNS0xLjEtLjQ4LTIuMjg1LS43MzUtMy40ODYtLjc1LTEuMi0uMDE0LTIuMzkyLjIxMS0zLjUwNC42NjQtLjgxNy4zMzMtMS41OC43ODMtMi4yNjQgMS4zMzYgMS41NDYgMS4yNSAzLjUxNCAyIDUuNjU3IDJ6bTQuMzk3LTUuMDgzYy45NjcuNDIyIDEuODY2Ljk4IDIuNjcyIDEuNjU1QzIwLjI3OSAxNi4wMzkgMjEgMTQuMTA0IDIxIDEyYzAtNC45Ny00LjAzLTktOS05cy05IDQuMDMtOSA5YzAgMi4xMDQuNzIyIDQuMDQgMS45MzIgNS41NzIuODc0LS43MzQgMS44Ni0xLjMyOCAyLjkyMS0xLjc2IDEuMzYtLjU1NCAyLjgxNi0uODMgNC4yODMtLjgxMSAxLjQ2Ny4wMTggMi45MTYuMzMgNC4yNi45MTZ6TTEyIDIzYzYuMDc1IDAgMTEtNC45MjUgMTEtMTFTMTguMDc1IDEgMTIgMSAxIDUuOTI1IDEgMTJzNC45MjUgMTEgMTEgMTF6bTMtMTNjMCAxLjY1Ny0xLjM0MyAzLTMgM3MtMy0xLjM0My0zLTMgMS4zNDMtMyAzLTMgMyAxLjM0MyAzIDN6bTIgMGMwIDIuNzYxLTIuMjM5IDUtNSA1cy01LTIuMjM5LTUtNSAyLjIzOS01IDUtNSA1IDIuMjM5IDUgNXoiIGZpbGw9IiMwMDAiLz4KPC9zdmc+Cg=="; exports.ddgIdentityIconBase = ddgIdentityIconBase; -},{}],58:[function(require,module,exports){ +},{}],59:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CSS_STYLES = void 0; -const CSS_STYLES = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n /* move it offscreen to avoid flashing */\n transform: translate(-1000px);\n z-index: 2147483647;\n}\n:not(.top-autofill).wrapper--data {\n font-family: 'SF Pro Text', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 46px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email::before,\n.tooltip--email::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: 20px;\n}\n.tooltip--email::before {\n border-bottom-color: #D0D0D0;\n top: -9px;\n}\n.tooltip--email::after {\n border-bottom-color: #FFFFFF;\n top: -8px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.tooltip__button:hover {\n background-color: rgba(0, 121, 242, 0.9);\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 24px 24px;\n background-repeat: no-repeat;\n background-position: center 4px;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n line-height: 13px;\n}\n.label + .label {\n margin-top: 5px;\n}\n.label.label--medium {\n letter-spacing: -0.08px;\n color: rgba(0,0,0,.9)\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label,\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before {\n /* TODO: use dynamically from src/UI/img/ddgPasswordIcon.js */\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjYzNiA4LjY4MkM5LjYzNiA1LjU0NCAxMi4xOCAzIDE1LjMxOCAzIDE4LjQ1NiAzIDIxIDUuNTQ0IDIxIDguNjgyYzAgMy4xMzgtMi41NDQgNS42ODItNS42ODIgNS42ODItLjY5MiAwLTEuMzUzLS4xMjQtMS45NjQtLjM0OS0uMzcyLS4xMzctLjc5LS4wNDEtMS4wNjYuMjQ1bC0uNzEzLjc0SDEwYy0uNTUyIDAtMSAuNDQ4LTEgMXYySDdjLS41NTIgMC0xIC40NDgtMSAxdjJIM3YtMi44ODFsNi42NjgtNi42NjhjLjI2NS0uMjY2LjM2LS42NTguMjQ0LTEuMDE1LS4xNzktLjU1MS0uMjc2LTEuMTQtLjI3Ni0xLjc1NHpNMTUuMzE4IDFjLTQuMjQyIDAtNy42ODIgMy40NC03LjY4MiA3LjY4MiAwIC42MDcuMDcxIDEuMi4yMDUgMS43NjdsLTYuNTQ4IDYuNTQ4Yy0uMTg4LjE4OC0uMjkzLjQ0Mi0uMjkzLjcwOFYyMmMwIC4yNjUuMTA1LjUyLjI5My43MDcuMTg3LjE4OC40NDIuMjkzLjcwNy4yOTNoNGMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMuMjcyIDAgLjUzMi0uMTEuNzItLjMwNmwuNTc3LS42Yy42NDUuMTc2IDEuMzIzLjI3IDIuMDIxLjI3IDQuMjQzIDAgNy42ODItMy40NCA3LjY4Mi03LjY4MkMyMyA0LjQzOSAxOS41NiAxIDE1LjMxOCAxek0xNSA4YzAtLjU1Mi40NDgtMSAxLTFzMSAuNDQ4IDEgMS0uNDQ4IDEtMSAxLTEtLjQ0OC0xLTF6bTEtM2MtMS42NTcgMC0zIDEuMzQzLTMgM3MxLjM0MyAzIDMgMyAzLTEuMzQzIDMtMy0xLjM0My0zLTMtM3oiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjkiLz4KPC9zdmc+');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 10px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgNDQgNDQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGxpbmVhckdyYWRpZW50IGlkPSJhIj48c3RvcCBvZmZzZXQ9Ii4wMSIgc3RvcC1jb2xvcj0iIzYxNzZiOSIvPjxzdG9wIG9mZnNldD0iLjY5IiBzdG9wLWNvbG9yPSIjMzk0YTlmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTMuOTI5NyIgeDI9IjE3LjA3MiIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNi4zOTgiIHkyPSIxNi4zOTgiLz48bGluZWFyR3JhZGllbnQgaWQ9ImMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMjMuODExNSIgeDI9IjI2LjY3NTIiIHhsaW5rOmhyZWY9IiNhIiB5MT0iMTQuOTY3OSIgeTI9IjE0Ljk2NzkiLz48bWFzayBpZD0iZCIgaGVpZ2h0PSI0MCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgd2lkdGg9IjQwIiB4PSIyIiB5PSIyIj48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0yMi4wMDAzIDQxLjA2NjljMTAuNTMwMiAwIDE5LjA2NjYtOC41MzY0IDE5LjA2NjYtMTkuMDY2NiAwLTEwLjUzMDMtOC41MzY0LTE5LjA2NjcxLTE5LjA2NjYtMTkuMDY2NzEtMTAuNTMwMyAwLTE5LjA2NjcxIDguNTM2NDEtMTkuMDY2NzEgMTkuMDY2NzEgMCAxMC41MzAyIDguNTM2NDEgMTkuMDY2NiAxOS4wNjY3MSAxOS4wNjY2eiIgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9tYXNrPjxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTIyIDQ0YzEyLjE1MDMgMCAyMi05Ljg0OTcgMjItMjIgMC0xMi4xNTAyNi05Ljg0OTctMjItMjItMjItMTIuMTUwMjYgMC0yMiA5Ljg0OTc0LTIyIDIyIDAgMTIuMTUwMyA5Ljg0OTc0IDIyIDIyIDIyeiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PGcgbWFzaz0idXJsKCNkKSI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMjYuMDgxMyA0MS42Mzg2Yy0uOTIwMy0xLjc4OTMtMS44MDAzLTMuNDM1Ni0yLjM0NjYtNC41MjQ2LTEuNDUyLTIuOTA3Ny0yLjkxMTQtNy4wMDctMi4yNDc3LTkuNjUwNy4xMjEtLjQ4MDMtMS4zNjc3LTE3Ljc4Njk5LTIuNDItMTguMzQ0MzItMS4xNjk3LS42MjMzMy0zLjcxMDctMS40NDQ2Ny01LjAyNy0xLjY2NDY3LS45MTY3LS4xNDY2Ni0xLjEyNTcuMTEtMS41MTA3LjE2ODY3LjM2My4wMzY2NyAyLjA5Ljg4NzMzIDIuNDIzNy45MzUtLjMzMzcuMjI3MzMtMS4zMi0uMDA3MzMtMS45NTA3LjI3MTMzLS4zMTkuMTQ2NjctLjU1NzMuNjg5MzQtLjU1Ljk0NiAxLjc5NjctLjE4MzMzIDQuNjA1NC0uMDAzNjYgNi4yNy43MzMyOS0xLjMyMzYuMTUwNC0zLjMzMy4zMTktNC4xOTgzLjc3MzctMi41MDggMS4zMi0zLjYxNTMgNC40MTEtMi45NTUzIDguMTE0My42NTYzIDMuNjk2IDMuNTY0IDE3LjE3ODQgNC40OTE2IDIxLjY4MS45MjQgNC40OTkgMTEuNTUzNyAzLjU1NjcgMTAuMDE3NC41NjF6IiBmaWxsPSIjZDVkN2Q4IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJtMjIuMjg2NSAyNi44NDM5Yy0uNjYgMi42NDM2Ljc5MiA2LjczOTMgMi4yNDc2IDkuNjUwNi40ODkxLjk3MjcgMS4yNDM4IDIuMzkyMSAyLjA1NTggMy45NjM3LTEuODk0LjQ2OTMtNi40ODk1IDEuMTI2NC05LjcxOTEgMC0uOTI0LTQuNDkxNy0zLjgzMTctMTcuOTc3Ny00LjQ5NTMtMjEuNjgxLS42Ni0zLjcwMzMgMC02LjM0NyAyLjUxNTMtNy42NjcuODYxNy0uNDU0NyAyLjA5MzctLjc4NDcgMy40MTM3LS45MzEzLTEuNjY0Ny0uNzQwNy0zLjYzNzQtMS4wMjY3LTUuNDQxNC0uODQzMzYtLjAwNzMtLjc2MjY3IDEuMzM4NC0uNzE4NjcgMS44NDQ0LTEuMDYzMzQtLjMzMzctLjA0NzY2LTEuMTYyNC0uNzk1NjYtMS41MjktLjgzMjMzIDIuMjg4My0uMzkyNDQgNC42NDIzLS4wMjEzOCA2LjY5OSAxLjA1NiAxLjA0ODYuNTYxIDEuNzg5MyAxLjE2MjMzIDIuMjQ3NiAxLjc5MzAzIDEuMTk1NC4yMjczIDIuMjUxNC42NiAyLjk0MDcgMS4zNDkzIDIuMTE5MyAyLjExNTcgNC4wMTEzIDYuOTUyIDMuMjE5MyA5LjczMTMtLjIyMzYuNzctLjczMzMgMS4zMzEtMS4zNzEzIDEuNzk2Ny0xLjIzOTMuOTAyLTEuMDE5My0xLjA0NS00LjEwMy45NzE3LS4zOTk3LjI2MDMtLjM5OTcgMi4yMjU2LS41MjQzIDIuNzA2eiIgZmlsbD0iI2ZmZiIvPjwvZz48ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PHBhdGggZD0ibTE2LjY3MjQgMjAuMzU0Yy43Njc1IDAgMS4zODk2LS42MjIxIDEuMzg5Ni0xLjM4OTZzLS42MjIxLTEuMzg5Ny0xLjM4OTYtMS4zODk3LTEuMzg5Ny42MjIyLTEuMzg5NyAxLjM4OTcuNjIyMiAxLjM4OTYgMS4zODk3IDEuMzg5NnoiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMTcuMjkyNCAxOC44NjE3Yy4xOTg1IDAgLjM1OTQtLjE2MDguMzU5NC0uMzU5M3MtLjE2MDktLjM1OTMtLjM1OTQtLjM1OTNjLS4xOTg0IDAtLjM1OTMuMTYwOC0uMzU5My4zNTkzcy4xNjA5LjM1OTMuMzU5My4zNTkzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im0yNS45NTY4IDE5LjMzMTFjLjY1ODEgMCAxLjE5MTctLjUzMzUgMS4xOTE3LTEuMTkxNyAwLS42NTgxLS41MzM2LTEuMTkxNi0xLjE5MTctMS4xOTE2cy0xLjE5MTcuNTMzNS0xLjE5MTcgMS4xOTE2YzAgLjY1ODIuNTMzNiAxLjE5MTcgMS4xOTE3IDEuMTkxN3oiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMjYuNDg4MiAxOC4wNTExYy4xNzAxIDAgLjMwOC0uMTM3OS4zMDgtLjMwOHMtLjEzNzktLjMwOC0uMzA4LS4zMDgtLjMwOC4xMzc5LS4zMDguMzA4LjEzNzkuMzA4LjMwOC4zMDh6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0ibTE3LjA3MiAxNC45NDJzLTEuMDQ4Ni0uNDc2Ni0yLjA2NDMuMTY1Yy0xLjAxNTcuNjM4LS45NzkgMS4yOTA3LS45NzkgMS4yOTA3cy0uNTM5LTEuMjAyNy44OTgzLTEuNzkzYzEuNDQxLS41ODY3IDIuMTQ1LjMzNzMgMi4xNDUuMzM3M3oiIGZpbGw9InVybCgjYikiLz48cGF0aCBkPSJtMjYuNjc1MiAxNC44NDY3cy0uNzUxNy0uNDI5LTEuMzM4My0uNDIxN2MtMS4xOTkuMDE0Ny0xLjUyNTQuNTQyNy0xLjUyNTQuNTQyN3MuMjAxNy0xLjI2MTQgMS43MzQ0LTEuMDA4NGMuNDk5Ny4wOTE0LjkyMjMuNDIzNCAxLjEyOTMuODg3NHoiIGZpbGw9InVybCgjYykiLz48cGF0aCBkPSJtMjAuOTI1OCAyNC4zMjFjLjEzOTMtLjg0MzMgMi4zMS0yLjQzMSAzLjg1LTIuNTMgMS41NC0uMDk1MyAyLjAxNjctLjA3MzMgMy4zLS4zODEzIDEuMjg3LS4zMDQzIDQuNTk4LTEuMTI5MyA1LjUxMS0xLjU1NDcuOTE2Ny0uNDIxNiA0LjgwMzMuMjA5IDIuMDY0MyAxLjczOC0xLjE4NDMuNjYzNy00LjM3OCAxLjg4MS02LjY2MjMgMi41NjMtMi4yODA3LjY4Mi0zLjY2My0uNjUyNi00LjQyMi40Njk0LS42MDEzLjg5MS0uMTIxIDIuMTEyIDIuNjAzMyAyLjM2NSAzLjY4MTQuMzQxIDcuMjA4Ny0xLjY1NzQgNy41OTc0LS41OTQuMzg4NiAxLjA2MzMtMy4xNjA3IDIuMzgzMy01LjMyNCAyLjQyNzMtMi4xNjM0LjA0MDMtNi41MTk0LTEuNDMtNy4xNzItMS44ODQ3LS42NTY0LS40NTEtMS41MjU0LTEuNTE0My0xLjM0NTctMi42MTh6IiBmaWxsPSIjZmRkMjBhIi8+PHBhdGggZD0ibTI4Ljg4MjUgMzEuODM4NmMtLjc3NzMtLjE3MjQtNC4zMTIgMi41MDA2LTQuMzEyIDIuNTAwNmguMDAzN2wtLjE2NSAyLjA1MzRzNC4wNDA2IDEuNjUzNiA0LjczIDEuMzk3Yy42ODkzLS4yNjQuNTE3LTUuNzc1LS4yNTY3LTUuOTUxem0tMTEuNTQ2MyAxLjAzNGMuMDg0My0xLjExODQgNS4yNTQzIDEuNjQyNiA1LjI1NDMgMS42NDI2bC4wMDM3LS4wMDM2LjI1NjYgMi4xNTZzLTQuMzA4MyAyLjU4MTMtNC45MTMzIDIuMjM2NmMtLjYwMTMtLjM0NDYtLjY4OTMtNC45MDk2LS42MDEzLTYuMDMxNnoiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjEuMzQgMzQuODA0OWMwIDEuODA3Ny0uMjYwNCAyLjU4NS41MTMzIDIuNzU3NC43NzczLjE3MjMgMi4yNDAzIDAgMi43NjEtLjM0NDcuNTEzMy0uMzQ0Ny4wODQzLTIuNjY5My0uMDg4LTMuMTAycy0zLjE5LS4wODgtMy4xOS42ODkzeiIgZmlsbD0iIzQzYTI0NCIvPjxwYXRoIGQ9Im0yMS42NzAxIDM0LjQwNTFjMCAxLjgwNzYtLjI2MDQgMi41ODEzLjUxMzMgMi43NTM2Ljc3MzcuMTc2IDIuMjM2NyAwIDIuNzU3My0uMzQ0Ni41MTctLjM0NDcuMDg4LTIuNjY5NC0uMDg0My0zLjEwMi0uMTcyMy0uNDMyNy0zLjE5LS4wODQ0LTMuMTkuNjg5M3oiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjIuMDAwMiA0MC40NDgxYzEwLjE4ODUgMCAxOC40NDc5LTguMjU5NCAxOC40NDc5LTE4LjQ0NzlzLTguMjU5NC0xOC40NDc5NS0xOC40NDc5LTE4LjQ0Nzk1LTE4LjQ0Nzk1IDguMjU5NDUtMTguNDQ3OTUgMTguNDQ3OTUgOC4yNTk0NSAxOC40NDc5IDE4LjQ0Nzk1IDE4LjQ0Nzl6bTAgMS43MTg3YzExLjEzNzcgMCAyMC4xNjY2LTkuMDI4OSAyMC4xNjY2LTIwLjE2NjYgMC0xMS4xMzc4LTkuMDI4OS0yMC4xNjY3LTIwLjE2NjYtMjAuMTY2Ny0xMS4xMzc4IDAtMjAuMTY2NyA5LjAyODktMjAuMTY2NyAyMC4xNjY3IDAgMTEuMTM3NyA5LjAyODkgMjAuMTY2NiAyMC4xNjY3IDIwLjE2NjZ6IiBmaWxsPSIjZmZmIi8+PC9nPjwvc3ZnPg==');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n"; +const CSS_STYLES = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n /* move it offscreen to avoid flashing */\n transform: translate(-1000px);\n z-index: 2147483647;\n}\n:not(.top-autofill).wrapper--data {\n font-family: 'SF Pro Text', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 46px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email::before,\n.tooltip--email::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: 20px;\n}\n.tooltip--email::before {\n border-bottom-color: #D0D0D0;\n top: -9px;\n}\n.tooltip--email::after {\n border-bottom-color: #FFFFFF;\n top: -8px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.tooltip__button:hover {\n background-color: rgba(0, 121, 242, 0.9);\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 24px 24px;\n background-repeat: no-repeat;\n background-position: center 4px;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n line-height: 13px;\n}\n.label + .label {\n margin-top: 5px;\n}\n.label.label--medium {\n letter-spacing: -0.08px;\n color: rgba(0,0,0,.9)\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label,\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before {\n /* TODO: use dynamically from src/UI/img/ddgPasswordIcon.js */\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjYzNiA4LjY4MkM5LjYzNiA1LjU0NCAxMi4xOCAzIDE1LjMxOCAzIDE4LjQ1NiAzIDIxIDUuNTQ0IDIxIDguNjgyYzAgMy4xMzgtMi41NDQgNS42ODItNS42ODIgNS42ODItLjY5MiAwLTEuMzUzLS4xMjQtMS45NjQtLjM0OS0uMzcyLS4xMzctLjc5LS4wNDEtMS4wNjYuMjQ1bC0uNzEzLjc0SDEwYy0uNTUyIDAtMSAuNDQ4LTEgMXYySDdjLS41NTIgMC0xIC40NDgtMSAxdjJIM3YtMi44ODFsNi42NjgtNi42NjhjLjI2NS0uMjY2LjM2LS42NTguMjQ0LTEuMDE1LS4xNzktLjU1MS0uMjc2LTEuMTQtLjI3Ni0xLjc1NHpNMTUuMzE4IDFjLTQuMjQyIDAtNy42ODIgMy40NC03LjY4MiA3LjY4MiAwIC42MDcuMDcxIDEuMi4yMDUgMS43NjdsLTYuNTQ4IDYuNTQ4Yy0uMTg4LjE4OC0uMjkzLjQ0Mi0uMjkzLjcwOFYyMmMwIC4yNjUuMTA1LjUyLjI5My43MDcuMTg3LjE4OC40NDIuMjkzLjcwNy4yOTNoNGMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMuMjcyIDAgLjUzMi0uMTEuNzItLjMwNmwuNTc3LS42Yy42NDUuMTc2IDEuMzIzLjI3IDIuMDIxLjI3IDQuMjQzIDAgNy42ODItMy40NCA3LjY4Mi03LjY4MkMyMyA0LjQzOSAxOS41NiAxIDE1LjMxOCAxek0xNSA4YzAtLjU1Mi40NDgtMSAxLTFzMSAuNDQ4IDEgMS0uNDQ4IDEtMSAxLTEtLjQ0OC0xLTF6bTEtM2MtMS42NTcgMC0zIDEuMzQzLTMgM3MxLjM0MyAzIDMgMyAzLTEuMzQzIDMtMy0xLjM0My0zLTMtM3oiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjkiLz4KPC9zdmc+');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 10px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgNDQgNDQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGxpbmVhckdyYWRpZW50IGlkPSJhIj48c3RvcCBvZmZzZXQ9Ii4wMSIgc3RvcC1jb2xvcj0iIzYxNzZiOSIvPjxzdG9wIG9mZnNldD0iLjY5IiBzdG9wLWNvbG9yPSIjMzk0YTlmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTMuOTI5NyIgeDI9IjE3LjA3MiIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNi4zOTgiIHkyPSIxNi4zOTgiLz48bGluZWFyR3JhZGllbnQgaWQ9ImMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMjMuODExNSIgeDI9IjI2LjY3NTIiIHhsaW5rOmhyZWY9IiNhIiB5MT0iMTQuOTY3OSIgeTI9IjE0Ljk2NzkiLz48bWFzayBpZD0iZCIgaGVpZ2h0PSI0MCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgd2lkdGg9IjQwIiB4PSIyIiB5PSIyIj48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0yMi4wMDAzIDQxLjA2NjljMTAuNTMwMiAwIDE5LjA2NjYtOC41MzY0IDE5LjA2NjYtMTkuMDY2NiAwLTEwLjUzMDMtOC41MzY0LTE5LjA2NjcxLTE5LjA2NjYtMTkuMDY2NzEtMTAuNTMwMyAwLTE5LjA2NjcxIDguNTM2NDEtMTkuMDY2NzEgMTkuMDY2NzEgMCAxMC41MzAyIDguNTM2NDEgMTkuMDY2NiAxOS4wNjY3MSAxOS4wNjY2eiIgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9tYXNrPjxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTIyIDQ0YzEyLjE1MDMgMCAyMi05Ljg0OTcgMjItMjIgMC0xMi4xNTAyNi05Ljg0OTctMjItMjItMjItMTIuMTUwMjYgMC0yMiA5Ljg0OTc0LTIyIDIyIDAgMTIuMTUwMyA5Ljg0OTc0IDIyIDIyIDIyeiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PGcgbWFzaz0idXJsKCNkKSI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMjYuMDgxMyA0MS42Mzg2Yy0uOTIwMy0xLjc4OTMtMS44MDAzLTMuNDM1Ni0yLjM0NjYtNC41MjQ2LTEuNDUyLTIuOTA3Ny0yLjkxMTQtNy4wMDctMi4yNDc3LTkuNjUwNy4xMjEtLjQ4MDMtMS4zNjc3LTE3Ljc4Njk5LTIuNDItMTguMzQ0MzItMS4xNjk3LS42MjMzMy0zLjcxMDctMS40NDQ2Ny01LjAyNy0xLjY2NDY3LS45MTY3LS4xNDY2Ni0xLjEyNTcuMTEtMS41MTA3LjE2ODY3LjM2My4wMzY2NyAyLjA5Ljg4NzMzIDIuNDIzNy45MzUtLjMzMzcuMjI3MzMtMS4zMi0uMDA3MzMtMS45NTA3LjI3MTMzLS4zMTkuMTQ2NjctLjU1NzMuNjg5MzQtLjU1Ljk0NiAxLjc5NjctLjE4MzMzIDQuNjA1NC0uMDAzNjYgNi4yNy43MzMyOS0xLjMyMzYuMTUwNC0zLjMzMy4zMTktNC4xOTgzLjc3MzctMi41MDggMS4zMi0zLjYxNTMgNC40MTEtMi45NTUzIDguMTE0My42NTYzIDMuNjk2IDMuNTY0IDE3LjE3ODQgNC40OTE2IDIxLjY4MS45MjQgNC40OTkgMTEuNTUzNyAzLjU1NjcgMTAuMDE3NC41NjF6IiBmaWxsPSIjZDVkN2Q4IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJtMjIuMjg2NSAyNi44NDM5Yy0uNjYgMi42NDM2Ljc5MiA2LjczOTMgMi4yNDc2IDkuNjUwNi40ODkxLjk3MjcgMS4yNDM4IDIuMzkyMSAyLjA1NTggMy45NjM3LTEuODk0LjQ2OTMtNi40ODk1IDEuMTI2NC05LjcxOTEgMC0uOTI0LTQuNDkxNy0zLjgzMTctMTcuOTc3Ny00LjQ5NTMtMjEuNjgxLS42Ni0zLjcwMzMgMC02LjM0NyAyLjUxNTMtNy42NjcuODYxNy0uNDU0NyAyLjA5MzctLjc4NDcgMy40MTM3LS45MzEzLTEuNjY0Ny0uNzQwNy0zLjYzNzQtMS4wMjY3LTUuNDQxNC0uODQzMzYtLjAwNzMtLjc2MjY3IDEuMzM4NC0uNzE4NjcgMS44NDQ0LTEuMDYzMzQtLjMzMzctLjA0NzY2LTEuMTYyNC0uNzk1NjYtMS41MjktLjgzMjMzIDIuMjg4My0uMzkyNDQgNC42NDIzLS4wMjEzOCA2LjY5OSAxLjA1NiAxLjA0ODYuNTYxIDEuNzg5MyAxLjE2MjMzIDIuMjQ3NiAxLjc5MzAzIDEuMTk1NC4yMjczIDIuMjUxNC42NiAyLjk0MDcgMS4zNDkzIDIuMTE5MyAyLjExNTcgNC4wMTEzIDYuOTUyIDMuMjE5MyA5LjczMTMtLjIyMzYuNzctLjczMzMgMS4zMzEtMS4zNzEzIDEuNzk2Ny0xLjIzOTMuOTAyLTEuMDE5My0xLjA0NS00LjEwMy45NzE3LS4zOTk3LjI2MDMtLjM5OTcgMi4yMjU2LS41MjQzIDIuNzA2eiIgZmlsbD0iI2ZmZiIvPjwvZz48ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PHBhdGggZD0ibTE2LjY3MjQgMjAuMzU0Yy43Njc1IDAgMS4zODk2LS42MjIxIDEuMzg5Ni0xLjM4OTZzLS42MjIxLTEuMzg5Ny0xLjM4OTYtMS4zODk3LTEuMzg5Ny42MjIyLTEuMzg5NyAxLjM4OTcuNjIyMiAxLjM4OTYgMS4zODk3IDEuMzg5NnoiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMTcuMjkyNCAxOC44NjE3Yy4xOTg1IDAgLjM1OTQtLjE2MDguMzU5NC0uMzU5M3MtLjE2MDktLjM1OTMtLjM1OTQtLjM1OTNjLS4xOTg0IDAtLjM1OTMuMTYwOC0uMzU5My4zNTkzcy4xNjA5LjM1OTMuMzU5My4zNTkzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im0yNS45NTY4IDE5LjMzMTFjLjY1ODEgMCAxLjE5MTctLjUzMzUgMS4xOTE3LTEuMTkxNyAwLS42NTgxLS41MzM2LTEuMTkxNi0xLjE5MTctMS4xOTE2cy0xLjE5MTcuNTMzNS0xLjE5MTcgMS4xOTE2YzAgLjY1ODIuNTMzNiAxLjE5MTcgMS4xOTE3IDEuMTkxN3oiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMjYuNDg4MiAxOC4wNTExYy4xNzAxIDAgLjMwOC0uMTM3OS4zMDgtLjMwOHMtLjEzNzktLjMwOC0uMzA4LS4zMDgtLjMwOC4xMzc5LS4zMDguMzA4LjEzNzkuMzA4LjMwOC4zMDh6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0ibTE3LjA3MiAxNC45NDJzLTEuMDQ4Ni0uNDc2Ni0yLjA2NDMuMTY1Yy0xLjAxNTcuNjM4LS45NzkgMS4yOTA3LS45NzkgMS4yOTA3cy0uNTM5LTEuMjAyNy44OTgzLTEuNzkzYzEuNDQxLS41ODY3IDIuMTQ1LjMzNzMgMi4xNDUuMzM3M3oiIGZpbGw9InVybCgjYikiLz48cGF0aCBkPSJtMjYuNjc1MiAxNC44NDY3cy0uNzUxNy0uNDI5LTEuMzM4My0uNDIxN2MtMS4xOTkuMDE0Ny0xLjUyNTQuNTQyNy0xLjUyNTQuNTQyN3MuMjAxNy0xLjI2MTQgMS43MzQ0LTEuMDA4NGMuNDk5Ny4wOTE0LjkyMjMuNDIzNCAxLjEyOTMuODg3NHoiIGZpbGw9InVybCgjYykiLz48cGF0aCBkPSJtMjAuOTI1OCAyNC4zMjFjLjEzOTMtLjg0MzMgMi4zMS0yLjQzMSAzLjg1LTIuNTMgMS41NC0uMDk1MyAyLjAxNjctLjA3MzMgMy4zLS4zODEzIDEuMjg3LS4zMDQzIDQuNTk4LTEuMTI5MyA1LjUxMS0xLjU1NDcuOTE2Ny0uNDIxNiA0LjgwMzMuMjA5IDIuMDY0MyAxLjczOC0xLjE4NDMuNjYzNy00LjM3OCAxLjg4MS02LjY2MjMgMi41NjMtMi4yODA3LjY4Mi0zLjY2My0uNjUyNi00LjQyMi40Njk0LS42MDEzLjg5MS0uMTIxIDIuMTEyIDIuNjAzMyAyLjM2NSAzLjY4MTQuMzQxIDcuMjA4Ny0xLjY1NzQgNy41OTc0LS41OTQuMzg4NiAxLjA2MzMtMy4xNjA3IDIuMzgzMy01LjMyNCAyLjQyNzMtMi4xNjM0LjA0MDMtNi41MTk0LTEuNDMtNy4xNzItMS44ODQ3LS42NTY0LS40NTEtMS41MjU0LTEuNTE0My0xLjM0NTctMi42MTh6IiBmaWxsPSIjZmRkMjBhIi8+PHBhdGggZD0ibTI4Ljg4MjUgMzEuODM4NmMtLjc3NzMtLjE3MjQtNC4zMTIgMi41MDA2LTQuMzEyIDIuNTAwNmguMDAzN2wtLjE2NSAyLjA1MzRzNC4wNDA2IDEuNjUzNiA0LjczIDEuMzk3Yy42ODkzLS4yNjQuNTE3LTUuNzc1LS4yNTY3LTUuOTUxem0tMTEuNTQ2MyAxLjAzNGMuMDg0My0xLjExODQgNS4yNTQzIDEuNjQyNiA1LjI1NDMgMS42NDI2bC4wMDM3LS4wMDM2LjI1NjYgMi4xNTZzLTQuMzA4MyAyLjU4MTMtNC45MTMzIDIuMjM2NmMtLjYwMTMtLjM0NDYtLjY4OTMtNC45MDk2LS42MDEzLTYuMDMxNnoiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjEuMzQgMzQuODA0OWMwIDEuODA3Ny0uMjYwNCAyLjU4NS41MTMzIDIuNzU3NC43NzczLjE3MjMgMi4yNDAzIDAgMi43NjEtLjM0NDcuNTEzMy0uMzQ0Ny4wODQzLTIuNjY5My0uMDg4LTMuMTAycy0zLjE5LS4wODgtMy4xOS42ODkzeiIgZmlsbD0iIzQzYTI0NCIvPjxwYXRoIGQ9Im0yMS42NzAxIDM0LjQwNTFjMCAxLjgwNzYtLjI2MDQgMi41ODEzLjUxMzMgMi43NTM2Ljc3MzcuMTc2IDIuMjM2NyAwIDIuNzU3My0uMzQ0Ni41MTctLjM0NDcuMDg4LTIuNjY5NC0uMDg0My0zLjEwMi0uMTcyMy0uNDMyNy0zLjE5LS4wODQ0LTMuMTkuNjg5M3oiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjIuMDAwMiA0MC40NDgxYzEwLjE4ODUgMCAxOC40NDc5LTguMjU5NCAxOC40NDc5LTE4LjQ0NzlzLTguMjU5NC0xOC40NDc5NS0xOC40NDc5LTE4LjQ0Nzk1LTE4LjQ0Nzk1IDguMjU5NDUtMTguNDQ3OTUgMTguNDQ3OTUgOC4yNTk0NSAxOC40NDc5IDE4LjQ0Nzk1IDE4LjQ0Nzl6bTAgMS43MTg3YzExLjEzNzcgMCAyMC4xNjY2LTkuMDI4OSAyMC4xNjY2LTIwLjE2NjYgMC0xMS4xMzc4LTkuMDI4OS0yMC4xNjY3LTIwLjE2NjYtMjAuMTY2Ny0xMS4xMzc4IDAtMjAuMTY2NyA5LjAyODktMjAuMTY2NyAyMC4xNjY3IDAgMTEuMTM3NyA5LjAyODkgMjAuMTY2NiAyMC4xNjY3IDIwLjE2NjZ6IiBmaWxsPSIjZmZmIi8+PC9nPjwvc3ZnPg==');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n\n/* Email Protection signup notice */\n:not(.top-autofill) .tooltip--email-signup {\n color: #222222;\n padding: 20px;\n width: 380px;\n}\n\n.tooltip--email-signup h1 {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.5;\n margin: 0;\n}\n\n.tooltip--email-signup p {\n font-weight: 400;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.notice-controls {\n display: flex;\n}\n\n.tooltip--email-signup .notice-controls > * {\n border-radius: 8px;\n border: 0;\n cursor: pointer;\n display: inline-block;\n font-style: normal;\n font-weight: bold;\n padding: 8px 12px;\n text-decoration: none;\n}\n\n.notice-controls .ghost {\n margin-left: 1rem;\n}\n\n.tooltip--email-signup a.primary {\n background: #3969EF;\n color: #fff;\n}\n\n.tooltip--email-signup a.primary:hover,\n.tooltip--email-signup a.primary:focus {\n background: #2b55ca;\n}\n\n.tooltip--email-signup a.primary:active {\n background: #1e42a4;\n}\n\n.tooltip--email-signup button.ghost {\n background: transparent;\n color: #3969EF;\n}\n\n.tooltip--email-signup button.ghost:hover,\n.tooltip--email-signup button.ghost:focus {\n background-color: rgba(0, 0, 0, 0.06);\n color: #2b55ca;\n}\n\n.tooltip--email-signup button.ghost:active {\n background-color: rgba(0, 0, 0, 0.12);\n color: #1e42a4;\n}"; exports.CSS_STYLES = CSS_STYLES; -},{}],59:[function(require,module,exports){ +},{}],60:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -15858,7 +15991,9 @@ const fireEventsOnSelect = el => { const setValueForSelect = (el, val) => { const subtype = (0, _matching.getInputSubtype)(el); const isMonth = subtype.includes('Month'); - const isZeroBasedNumber = isMonth && el.options[0].value === '0' && el.options.length === 12; // Loop first through all values because they tend to be more precise + const isZeroBasedNumber = isMonth && el.options[0].value === '0' && el.options.length === 12; + const stringVal = String(val); + const numberVal = Number(val); // Loop first through all values because they tend to be more precise for (const option of el.options) { // If values for months are zero-based (Jan === 0), add one to match our data type @@ -15870,7 +16005,7 @@ const setValueForSelect = (el, val) => { // TODO: implement alternative versions of values (abbreviations for States/Provinces or variations like USA, US, United States, etc.) - if (value === String(val)) { + if (value === stringVal || Number(value) === numberVal) { if (option.selected) return false; option.selected = true; fireEventsOnSelect(el); @@ -15879,7 +16014,7 @@ const setValueForSelect = (el, val) => { } for (const option of el.options) { - if (option.innerText === String(val)) { + if (option.innerText === stringVal || Number(option.innerText) === numberVal) { if (option.selected) return false; option.selected = true; fireEventsOnSelect(el); @@ -16126,7 +16261,7 @@ const getText = el => { exports.getText = getText; -},{"./Form/matching.js":41}],60:[function(require,module,exports){ +},{"./Form/matching.js":41}],61:[function(require,module,exports){ "use strict"; require("./requestIdleCallback.js"); @@ -16155,7 +16290,7 @@ var _DeviceInterface = require("./DeviceInterface.js"); } })(); -},{"./DeviceInterface.js":22,"./requestIdleCallback.js":71}],61:[function(require,module,exports){ +},{"./DeviceInterface.js":22,"./requestIdleCallback.js":72}],62:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -16241,7 +16376,7 @@ function createGlobalConfig(overrides) { return config; } -},{}],62:[function(require,module,exports){ +},{}],63:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -16255,7 +16390,7 @@ const constants = { }; exports.constants = constants; -},{}],63:[function(require,module,exports){ +},{}],64:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -16488,13 +16623,13 @@ class SendJSPixelCall extends _deviceApi.DeviceApiCall { exports.SendJSPixelCall = SendJSPixelCall; -},{"../../../packages/device-api":14,"./validators.zod.js":64}],64:[function(require,module,exports){ +},{"../../../packages/device-api":14,"./validators.zod.js":65}],65:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.userPreferencesSchema = exports.triggerContextSchema = exports.storeFormDataSchema = exports.setSizeParamsSchema = exports.sendJSPixelParamsSchema = exports.selectedDetailParamsSchema = exports.runtimeConfigurationSchema = exports.providerStatusUpdatedSchema = exports.outgoingCredentialsSchema = exports.getRuntimeConfigurationResponseSchema = exports.getAvailableInputTypesResultSchema = exports.getAutofillInitDataResponseSchema = exports.getAutofillDataResponseSchema = exports.getAutofillDataRequestSchema = exports.getAutofillCredentialsResultSchema = exports.getAutofillCredentialsParamsSchema = exports.getAliasResultSchema = exports.getAliasParamsSchema = exports.genericErrorSchema = exports.credentialsSchema = exports.contentScopeSchema = exports.contentScopeFeaturesSchema = exports.contentScopeFeaturesItemSettingsSchema = exports.checkCredentialsProviderStatusResultSchema = exports.availableInputTypesSchema = exports.autofillSettingsSchema = exports.autofillFeatureTogglesSchema = exports.askToUnlockProviderResultSchema = void 0; +exports.userPreferencesSchema = exports.triggerContextSchema = exports.storeFormDataSchema = exports.setSizeParamsSchema = exports.sendJSPixelParamsSchema = exports.selectedDetailParamsSchema = exports.runtimeConfigurationSchema = exports.providerStatusUpdatedSchema = exports.outgoingCredentialsSchema = exports.getRuntimeConfigurationResponseSchema = exports.getAvailableInputTypesResultSchema = exports.getAutofillInitDataResponseSchema = exports.getAutofillDataResponseSchema = exports.getAutofillDataRequestSchema = exports.getAutofillCredentialsResultSchema = exports.getAutofillCredentialsParamsSchema = exports.getAliasResultSchema = exports.getAliasParamsSchema = exports.genericErrorSchema = exports.credentialsSchema = exports.contentScopeSchema = exports.checkCredentialsProviderStatusResultSchema = exports.availableInputTypesSchema = exports.autofillSettingsSchema = exports.autofillFeatureTogglesSchema = exports.askToUnlockProviderResultSchema = void 0; var _zod = require("zod"); @@ -16555,6 +16690,7 @@ const autofillFeatureTogglesSchema = _zod.z.object({ inputType_identities: _zod.z.boolean().optional(), inputType_creditCards: _zod.z.boolean().optional(), emailProtection: _zod.z.boolean().optional(), + emailProtection_incontext_signup: _zod.z.boolean().optional(), password_generation: _zod.z.boolean().optional(), credentials_saving: _zod.z.boolean().optional(), inlineIcon_credentials: _zod.z.boolean().optional(), @@ -16647,9 +16783,16 @@ const getAvailableInputTypesResultSchema = _zod.z.object({ exports.getAvailableInputTypesResultSchema = getAvailableInputTypesResultSchema; -const contentScopeFeaturesItemSettingsSchema = _zod.z.record(_zod.z.unknown()); +const contentScopeSchema = _zod.z.object({ + features: _zod.z.record(_zod.z.object({ + exceptions: _zod.z.array(_zod.z.unknown()), + state: _zod.z.union([_zod.z.literal("enabled"), _zod.z.literal("disabled")]), + settings: _zod.z.record(_zod.z.unknown()).optional() + })), + unprotectedTemporary: _zod.z.array(_zod.z.unknown()) +}); -exports.contentScopeFeaturesItemSettingsSchema = contentScopeFeaturesItemSettingsSchema; +exports.contentScopeSchema = contentScopeSchema; const userPreferencesSchema = _zod.z.object({ globalPrivacyControlValue: _zod.z.boolean().optional(), @@ -16665,13 +16808,13 @@ const userPreferencesSchema = _zod.z.object({ exports.userPreferencesSchema = userPreferencesSchema; -const contentScopeFeaturesSchema = _zod.z.record(_zod.z.object({ - exceptions: _zod.z.array(_zod.z.unknown()), - state: _zod.z.union([_zod.z.literal("enabled"), _zod.z.literal("disabled")]), - settings: contentScopeFeaturesItemSettingsSchema.optional() -})); +const runtimeConfigurationSchema = _zod.z.object({ + contentScope: contentScopeSchema, + userUnprotectedDomains: _zod.z.array(_zod.z.string()), + userPreferences: userPreferencesSchema +}); -exports.contentScopeFeaturesSchema = contentScopeFeaturesSchema; +exports.runtimeConfigurationSchema = runtimeConfigurationSchema; const selectedDetailParamsSchema = _zod.z.object({ data: _zod.z.record(_zod.z.unknown()), @@ -16681,7 +16824,7 @@ const selectedDetailParamsSchema = _zod.z.object({ exports.selectedDetailParamsSchema = selectedDetailParamsSchema; const sendJSPixelParamsSchema = _zod.z.object({ - pixelName: _zod.z.literal("autofill_identity") + pixelName: _zod.z.union([_zod.z.literal("autofill_identity"), _zod.z.literal("autofill_private_address"), _zod.z.literal("autofill_personal_address")]) }); exports.sendJSPixelParamsSchema = sendJSPixelParamsSchema; @@ -16733,20 +16876,13 @@ const getAutofillDataRequestSchema = _zod.z.object({ exports.getAutofillDataRequestSchema = getAutofillDataRequestSchema; -const contentScopeSchema = _zod.z.object({ - features: contentScopeFeaturesSchema, - unprotectedTemporary: _zod.z.array(_zod.z.unknown()) -}); - -exports.contentScopeSchema = contentScopeSchema; - -const runtimeConfigurationSchema = _zod.z.object({ - contentScope: contentScopeSchema, - userUnprotectedDomains: _zod.z.array(_zod.z.string()), - userPreferences: userPreferencesSchema +const getRuntimeConfigurationResponseSchema = _zod.z.object({ + type: _zod.z.literal("getRuntimeConfigurationResponse").optional(), + success: runtimeConfigurationSchema.optional(), + error: genericErrorSchema.optional() }); -exports.runtimeConfigurationSchema = runtimeConfigurationSchema; +exports.getRuntimeConfigurationResponseSchema = getRuntimeConfigurationResponseSchema; const storeFormDataSchema = _zod.z.object({ credentials: outgoingCredentialsSchema.optional() @@ -16754,15 +16890,7 @@ const storeFormDataSchema = _zod.z.object({ exports.storeFormDataSchema = storeFormDataSchema; -const getRuntimeConfigurationResponseSchema = _zod.z.object({ - type: _zod.z.literal("getRuntimeConfigurationResponse").optional(), - success: runtimeConfigurationSchema.optional(), - error: genericErrorSchema.optional() -}); - -exports.getRuntimeConfigurationResponseSchema = getRuntimeConfigurationResponseSchema; - -},{"zod":12}],65:[function(require,module,exports){ +},{"zod":12}],66:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -16803,7 +16931,7 @@ class GetAlias extends _index.DeviceApiCall { exports.GetAlias = GetAlias; -},{"../../packages/device-api/index.js":14,"./__generated__/validators.zod.js":64}],66:[function(require,module,exports){ +},{"../../packages/device-api/index.js":14,"./__generated__/validators.zod.js":65}],67:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -16961,7 +17089,7 @@ function androidSpecificAvailableInputTypes(globalConfig) { }; } -},{"../../../packages/device-api/index.js":14,"../__generated__/deviceApiCalls.js":63}],67:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":14,"../__generated__/deviceApiCalls.js":64}],68:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -17037,7 +17165,7 @@ function appleSpecificRuntimeConfiguration(globalConfig) { }; } -},{"../../../packages/device-api/index.js":14,"../__generated__/deviceApiCalls.js":63,"@duckduckgo/content-scope-utils":2}],68:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":14,"../__generated__/deviceApiCalls.js":64,"@duckduckgo/content-scope-utils":2}],69:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -17067,6 +17195,11 @@ class ExtensionTransport extends _index.DeviceApiTransport { if (deviceApiCall instanceof _deviceApiCalls.GetAvailableInputTypesCall) { return deviceApiCall.result(await extensionSpecificGetAvailableInputTypes()); + } // TODO: unify all calls to use deviceApiCall.method instead of all these if blocks + + + if (deviceApiCall instanceof _deviceApiCalls.SendJSPixelCall) { + return deviceApiCall.result(await extensionSpecificSendPixel(deviceApiCall.params.pixelName)); } throw new Error('not implemented yet for ' + deviceApiCall.method); @@ -17128,8 +17261,25 @@ async function getContentScopeConfig() { }); }); } +/** + * @param {import('../__generated__/validators-ts').SendJSPixelParams['pixelName']} pixelName + */ + + +async function extensionSpecificSendPixel(pixelName) { + return new Promise(resolve => { + chrome.runtime.sendMessage({ + messageType: 'sendJSPixel', + options: { + pixelName + } + }, () => { + resolve(true); + }); + }); +} -},{"../../../packages/device-api/index.js":14,"../../Settings.js":49,"../../autofill-utils.js":59,"../__generated__/deviceApiCalls.js":63}],69:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":14,"../../Settings.js":49,"../../autofill-utils.js":60,"../__generated__/deviceApiCalls.js":64}],70:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -17183,7 +17333,7 @@ function createTransport(globalConfig) { return new _extensionTransport.ExtensionTransport(globalConfig); } -},{"./android.transport.js":66,"./apple.transport.js":67,"./extension.transport.js":68,"./windows.transport.js":70}],70:[function(require,module,exports){ +},{"./android.transport.js":67,"./apple.transport.js":68,"./extension.transport.js":69,"./windows.transport.js":71}],71:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -17283,7 +17433,7 @@ function waitForWindowsResponse(responseId, options) { }); } -},{"../../../packages/device-api/index.js":14}],71:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":14}],72:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -17331,4 +17481,4 @@ window.cancelIdleCallback = window.cancelIdleCallback || function (id) { var _default = {}; exports.default = _default; -},{}]},{},[60]); +},{}]},{},[61]); diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 4b8718ac0..f4228bed0 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -2910,6 +2910,9 @@ module.exports={ "claro.com.br": { "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" }, + "classmates.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" + }, "clien.net": { "password-rules": "minlength: 5; required: lower, upper; required: digit;" }, @@ -3216,15 +3219,15 @@ module.exports={ "medicare.gov": { "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" }, + "member.everbridge.net": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, "metlife.com": { "password-rules": "minlength: 6; maxlength: 20;" }, "microsoft.com": { "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" }, - "minecraft.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: ascii-printable;" - }, "mintmobile.com": { "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" }, @@ -3249,6 +3252,9 @@ module.exports={ "myhealthrecord.com": { "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" }, + "mysedgwick.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" + }, "mysubaru.com": { "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" }, @@ -3616,7 +3622,7 @@ function createDevice() { return new _ExtensionInterface.ExtensionInterface(globalConfig, deviceApi, settings); } -},{"../packages/device-api/index.js":6,"./DeviceInterface/AndroidInterface.js":15,"./DeviceInterface/AppleDeviceInterface.js":16,"./DeviceInterface/AppleOverlayDeviceInterface.js":17,"./DeviceInterface/ExtensionInterface.js":18,"./DeviceInterface/WindowsInterface.js":20,"./DeviceInterface/WindowsOverlayDeviceInterface.js":21,"./Settings.js":41,"./config.js":53,"./deviceApiCalls/transports/transports.js":61}],15:[function(require,module,exports){ +},{"../packages/device-api/index.js":6,"./DeviceInterface/AndroidInterface.js":15,"./DeviceInterface/AppleDeviceInterface.js":16,"./DeviceInterface/AppleOverlayDeviceInterface.js":17,"./DeviceInterface/ExtensionInterface.js":18,"./DeviceInterface/WindowsInterface.js":20,"./DeviceInterface/WindowsOverlayDeviceInterface.js":21,"./Settings.js":41,"./config.js":54,"./deviceApiCalls/transports/transports.js":62}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3763,7 +3769,7 @@ class AndroidInterface extends _InterfacePrototype.default { exports.AndroidInterface = AndroidInterface; -},{"../UI/controllers/NativeUIController.js":46,"../autofill-utils.js":51,"./InterfacePrototype.js":19,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],16:[function(require,module,exports){ +},{"../UI/controllers/NativeUIController.js":47,"../autofill-utils.js":52,"./InterfacePrototype.js":19,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4220,7 +4226,7 @@ class AppleDeviceInterface extends _InterfacePrototype.default { exports.AppleDeviceInterface = AppleDeviceInterface; -},{"../../packages/device-api/index.js":6,"../Form/matching.js":33,"../UI/HTMLTooltip.js":44,"../UI/controllers/HTMLTooltipUIController.js":45,"../UI/controllers/NativeUIController.js":46,"../UI/controllers/OverlayUIController.js":47,"../autofill-utils.js":51,"../deviceApiCalls/__generated__/deviceApiCalls.js":55,"../deviceApiCalls/additionalDeviceApiCalls.js":57,"./InterfacePrototype.js":19,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],17:[function(require,module,exports){ +},{"../../packages/device-api/index.js":6,"../Form/matching.js":33,"../UI/HTMLTooltip.js":45,"../UI/controllers/HTMLTooltipUIController.js":46,"../UI/controllers/NativeUIController.js":47,"../UI/controllers/OverlayUIController.js":48,"../autofill-utils.js":52,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"../deviceApiCalls/additionalDeviceApiCalls.js":58,"./InterfacePrototype.js":19,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],17:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4293,10 +4299,13 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter if (signedIn) { await this.getAddresses(); - } // setup overlay API pieces - + } + } + async postInit() { + // setup overlay API pieces this.overlay.showImmediately(); + super.postInit(); } /** * In the top-frame scenario we override the base 'selectedDetail'. @@ -4319,6 +4328,8 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter } providerStatusUpdated(data) { + var _this$uiController; + const { credentials, availableInputTypes @@ -4327,7 +4338,7 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter this.settings.setAvailableInputTypes(availableInputTypes); this.storeLocalCredentials(credentials); // rerender the tooltip - this.uiController.updateItems(credentials); + (_this$uiController = this.uiController) === null || _this$uiController === void 0 ? void 0 : _this$uiController.updateItems(credentials); } firePixel(pixelName) { @@ -4340,7 +4351,7 @@ class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInter exports.AppleOverlayDeviceInterface = AppleOverlayDeviceInterface; -},{"../../packages/device-api/index.js":6,"../UI/controllers/HTMLTooltipUIController.js":45,"../deviceApiCalls/__generated__/deviceApiCalls.js":55,"../deviceApiCalls/__generated__/validators.zod.js":56,"./AppleDeviceInterface.js":16,"./overlayApi.js":23}],18:[function(require,module,exports){ +},{"../../packages/device-api/index.js":6,"../UI/controllers/HTMLTooltipUIController.js":46,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"../deviceApiCalls/__generated__/validators.zod.js":57,"./AppleDeviceInterface.js":16,"./overlayApi.js":23}],18:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4356,8 +4367,15 @@ var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIControlle var _HTMLTooltip = require("../UI/HTMLTooltip.js"); +var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const POPUP_TYPES = { + EmailProtection: 'EmailProtection', + EmailSignup: 'EmailSignup' +}; + class ExtensionInterface extends _InterfacePrototype.default { /** * @override @@ -4368,12 +4386,34 @@ class ExtensionInterface extends _InterfacePrototype.default { css: ""), testMode: this.isTestMode() }; + const tooltipKinds = { + [POPUP_TYPES.EmailProtection]: 'legacy', + [POPUP_TYPES.EmailSignup]: 'emailsignup' + }; + const tooltipKind = tooltipKinds[this.getShowingTooltip()] || tooltipKinds[POPUP_TYPES.EmailProtection]; return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: 'legacy', + tooltipKind, device: this }, htmlTooltipOptions); } + get hasDismissedEmailSignup() { + // TODO -- implement peristed dismissed timestamp + return false; + } + + getShowingTooltip() { + if (this.hasLocalAddresses) { + return POPUP_TYPES.EmailProtection; + } + + if (this.settings.featureToggles.emailProtection_incontext_signup && !this.hasDismissedEmailSignup) { + return POPUP_TYPES.EmailSignup; + } + + return null; + } + async isEnabled() { return new Promise(resolve => { chrome.runtime.sendMessage({ @@ -4396,9 +4436,24 @@ class ExtensionInterface extends _InterfacePrototype.default { } postInit() { - if (this.hasLocalAddresses) { - const cleanup = this.scanner.init(); - this.addLogoutListener(cleanup); + switch (this.getShowingTooltip()) { + case POPUP_TYPES.EmailProtection: + { + const cleanup = this.scanner.init(); + this.addLogoutListener(cleanup); + break; + } + + case POPUP_TYPES.EmailSignup: + { + this.scanner.init(); + break; + } + + default: + { + break; + } } } @@ -4410,6 +4465,12 @@ class ExtensionInterface extends _InterfacePrototype.default { return resolve(data); })); } + + firePixel(pixelName) { + this.deviceApi.notify(new _deviceApiCalls.SendJSPixelCall({ + pixelName + })); + } /** * Used by the email web app * Settings page displays data of the logged in user data @@ -4520,7 +4581,7 @@ class ExtensionInterface extends _InterfacePrototype.default { exports.ExtensionInterface = ExtensionInterface; -},{"../UI/HTMLTooltip.js":44,"../UI/controllers/HTMLTooltipUIController.js":45,"../autofill-utils.js":51,"./InterfacePrototype.js":19}],19:[function(require,module,exports){ +},{"../UI/HTMLTooltip.js":45,"../UI/controllers/HTMLTooltipUIController.js":46,"../autofill-utils.js":52,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":19}],19:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4600,10 +4661,12 @@ class InterfacePrototype { /** @type {import('../Scanner').Scanner} */ - /** @type {import("../UI/controllers/UIController.js").UIController} */ + /** @type {import("../UI/controllers/UIController.js").UIController | null} */ /** @type {import("../../packages/device-api").DeviceApi} */ + /** @type {boolean} */ + /** * @param {GlobalConfig} config * @param {import("../../packages/device-api").DeviceApi} deviceApi @@ -4638,6 +4701,8 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); + _defineProperty(this, "alreadyInitialized", void 0); + _classPrivateFieldInitSpec(this, _data2, { writable: true, value: { @@ -4650,11 +4715,12 @@ class InterfacePrototype { this.globalConfig = config; this.deviceApi = deviceApi; - this.uiController = this.createUIController(); this.settings = settings; + this.uiController = null; this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); + this.alreadyInitialized = false; } /** * Implementors should override this with a UI controller that suits @@ -4829,9 +4895,11 @@ class InterfacePrototype { } async startInit() { + if (this.alreadyInitialized) return; await this.refreshSettings(); this.addDeviceListeners(); - await this.setupAutofill(); // this is the temporary measure to support windows whilst we still have 'setupAutofill' + await this.setupAutofill(); + this.uiController = this.createUIController(); // this is the temporary measure to support windows whilst we still have 'setupAutofill' // eventually all interfaces will use this if (!this.isEnabledViaSettings()) { @@ -4844,6 +4912,8 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } + + this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -4885,12 +4955,19 @@ class InterfacePrototype { const isEnabled = await this.isEnabled(); if (!isEnabled) return; + const handler = async () => { + if (document.readyState === 'complete') { + await this.startInit(); + window.removeEventListener('load', handler); + document.removeEventListener('readystatechange', handler); + } + }; + if (document.readyState === 'complete') { - this.startInit(); + await this.startInit(); } else { - window.addEventListener('load', () => { - this.startInit(); - }); + window.addEventListener('load', handler); + document.addEventListener('readystatechange', handler); } } /** @@ -4977,6 +5054,8 @@ class InterfacePrototype { attachTooltip(form, input, click) { + var _this$uiController; + let trigger = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'userInitiated'; // Avoid flashing tooltip from background tabs on macOS if (document.visibilityState !== 'visible') return; // Only autoprompt on mobile devices @@ -5013,7 +5092,7 @@ class InterfacePrototype { // for example, generated passwords may get appended here const processedTopContext = this.preAttachTooltip(topContextData, input, form); - this.uiController.attach({ + (_this$uiController = this.uiController) === null || _this$uiController === void 0 ? void 0 : _this$uiController.attach({ input, form, click, @@ -5096,15 +5175,15 @@ class InterfacePrototype { } isTooltipActive() { - var _this$uiController$is, _this$uiController$is2, _this$uiController; + var _this$uiController$is, _this$uiController2, _this$uiController2$i; - return (_this$uiController$is = (_this$uiController$is2 = (_this$uiController = this.uiController).isActive) === null || _this$uiController$is2 === void 0 ? void 0 : _this$uiController$is2.call(_this$uiController)) !== null && _this$uiController$is !== void 0 ? _this$uiController$is : false; + return (_this$uiController$is = (_this$uiController2 = this.uiController) === null || _this$uiController2 === void 0 ? void 0 : (_this$uiController2$i = _this$uiController2.isActive) === null || _this$uiController2$i === void 0 ? void 0 : _this$uiController2$i.call(_this$uiController2)) !== null && _this$uiController$is !== void 0 ? _this$uiController$is : false; } removeTooltip() { - var _this$uiController$re, _this$uiController2; + var _this$uiController3, _this$uiController3$r; - return (_this$uiController$re = (_this$uiController2 = this.uiController).removeTooltip) === null || _this$uiController$re === void 0 ? void 0 : _this$uiController$re.call(_this$uiController2, 'interface'); + return (_this$uiController3 = this.uiController) === null || _this$uiController3 === void 0 ? void 0 : (_this$uiController3$r = _this$uiController3.removeTooltip) === null || _this$uiController3$r === void 0 ? void 0 : _this$uiController3$r.call(_this$uiController3, 'interface'); } async setupSettingsPage() { @@ -5211,7 +5290,7 @@ class InterfacePrototype { providerStatusUpdated(data) { try { - var _availableInputTypes$; + var _this$uiController4, _availableInputTypes$; const { credentials, @@ -5221,7 +5300,7 @@ class InterfacePrototype { this.settings.setAvailableInputTypes(availableInputTypes); this.storeLocalCredentials(credentials); // rerender the tooltip - this.uiController.updateItems(credentials); // If the tooltip is open on an autofill type that's not available, close it + (_this$uiController4 = this.uiController) === null || _this$uiController4 === void 0 ? void 0 : _this$uiController4.updateItems(credentials); // If the tooltip is open on an autofill type that's not available, close it const currentInputSubtype = (0, _matching.getSubtypeFromType)(this.getCurrentInputType()); @@ -5398,7 +5477,7 @@ class InterfacePrototype { var _default = InterfacePrototype; exports.default = _default; -},{"../../packages/device-api/index.js":6,"../Form/formatters.js":27,"../Form/matching.js":33,"../InputTypes/Credentials.js":36,"../PasswordGenerator.js":39,"../Scanner.js":40,"../Settings.js":41,"../UI/controllers/NativeUIController.js":46,"../autofill-utils.js":51,"../config.js":53,"../deviceApiCalls/__generated__/deviceApiCalls.js":55,"../deviceApiCalls/__generated__/validators.zod.js":56,"../deviceApiCalls/transports/transports.js":61,"./initFormSubmissionsApi.js":22}],20:[function(require,module,exports){ +},{"../../packages/device-api/index.js":6,"../Form/formatters.js":27,"../Form/matching.js":33,"../InputTypes/Credentials.js":36,"../PasswordGenerator.js":39,"../Scanner.js":40,"../Settings.js":41,"../UI/controllers/NativeUIController.js":47,"../autofill-utils.js":52,"../config.js":54,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"../deviceApiCalls/__generated__/validators.zod.js":57,"../deviceApiCalls/transports/transports.js":62,"./initFormSubmissionsApi.js":22}],20:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -5537,7 +5616,7 @@ class WindowsInterface extends _InterfacePrototype.default { exports.WindowsInterface = WindowsInterface; -},{"../UI/controllers/OverlayUIController.js":47,"../deviceApiCalls/__generated__/deviceApiCalls.js":55,"./InterfacePrototype.js":19}],21:[function(require,module,exports){ +},{"../UI/controllers/OverlayUIController.js":48,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":19}],21:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -5610,9 +5689,13 @@ class WindowsOverlayDeviceInterface extends _InterfacePrototype.default { async setupAutofill() { const response = await this.deviceApi.request(new _deviceApiCalls.GetAutofillInitDataCall(null)); // @ts-ignore - this.storeLocalData(response); // setup overlay API pieces + this.storeLocalData(response); + } + async postInit() { + // setup overlay API pieces this.overlay.showImmediately(); + super.postInit(); } /** * In the top-frame scenario, we send a message to the native @@ -5633,7 +5716,7 @@ class WindowsOverlayDeviceInterface extends _InterfacePrototype.default { exports.WindowsOverlayDeviceInterface = WindowsOverlayDeviceInterface; -},{"../UI/controllers/HTMLTooltipUIController.js":45,"../deviceApiCalls/__generated__/deviceApiCalls.js":55,"./InterfacePrototype.js":19,"./overlayApi.js":23}],22:[function(require,module,exports){ +},{"../UI/controllers/HTMLTooltipUIController.js":46,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":19,"./overlayApi.js":23}],22:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -5727,7 +5810,7 @@ function initFormSubmissionsApi(forms) { }); } -},{"../Form/matching.js":33,"../Form/selectors-css.js":34,"../autofill-utils.js":51}],23:[function(require,module,exports){ +},{"../Form/matching.js":33,"../Form/selectors-css.js":34,"../autofill-utils.js":52}],23:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -5751,9 +5834,9 @@ function overlayApi(device) { * page load every time it's opened. */ window.addEventListener('mouseMove', event => { - var _device$uiController$, _device$uiController; + var _device$uiController, _device$uiController$; - const activeTooltip = (_device$uiController$ = (_device$uiController = device.uiController).getActiveTooltip) === null || _device$uiController$ === void 0 ? void 0 : _device$uiController$.call(_device$uiController); + const activeTooltip = (_device$uiController = device.uiController) === null || _device$uiController === void 0 ? void 0 : (_device$uiController$ = _device$uiController.getActiveTooltip) === null || _device$uiController$ === void 0 ? void 0 : _device$uiController$.call(_device$uiController); activeTooltip === null || activeTooltip === void 0 ? void 0 : activeTooltip.focus(event.detail.x, event.detail.y); }); return { @@ -5761,7 +5844,7 @@ function overlayApi(device) { * When we are inside an 'overlay' - the HTML tooltip will be opened immediately */ showImmediately() { - var _device$uiController$2, _device$uiController2; + var _device$uiController2, _device$uiController3; const topContextData = device.getTopContextData(); if (!topContextData) throw new Error('unreachable, topContextData should be available'); // Provide dummy values @@ -5776,12 +5859,12 @@ function overlayApi(device) { }; // Create the tooltip, and set it as active - const tooltip = (_device$uiController$2 = (_device$uiController2 = device.uiController).createTooltip) === null || _device$uiController$2 === void 0 ? void 0 : _device$uiController$2.call(_device$uiController2, getPosition, topContextData); + const tooltip = (_device$uiController2 = device.uiController) === null || _device$uiController2 === void 0 ? void 0 : (_device$uiController3 = _device$uiController2.createTooltip) === null || _device$uiController3 === void 0 ? void 0 : _device$uiController3.call(_device$uiController2, getPosition, topContextData); if (tooltip) { - var _device$uiController$3, _device$uiController3; + var _device$uiController4, _device$uiController5; - (_device$uiController$3 = (_device$uiController3 = device.uiController).setActiveTooltip) === null || _device$uiController$3 === void 0 ? void 0 : _device$uiController$3.call(_device$uiController3, tooltip); + (_device$uiController4 = device.uiController) === null || _device$uiController4 === void 0 ? void 0 : (_device$uiController5 = _device$uiController4.setActiveTooltip) === null || _device$uiController5 === void 0 ? void 0 : _device$uiController5.call(_device$uiController4, tooltip); } }, @@ -5807,7 +5890,7 @@ function overlayApi(device) { }; } -},{"../deviceApiCalls/__generated__/deviceApiCalls.js":55}],24:[function(require,module,exports){ +},{"../deviceApiCalls/__generated__/deviceApiCalls.js":56}],24:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -5870,6 +5953,7 @@ class Form { this.formAnalyzer = new _FormAnalyzer.default(form, input, matching); this.isLogin = this.formAnalyzer.isLogin; this.isSignup = this.formAnalyzer.isSignup; + this.isHybrid = this.formAnalyzer.isHybrid; this.device = deviceInterface; /** @type Record<'all' | SupportedMainTypes, Set> */ @@ -5921,24 +6005,6 @@ class Form { /** @type HTMLElement */ e === null || e === void 0 ? void 0 : e.target); } - /** - * Checks that the form element doesn't contain an invalid field - * @return {boolean} - */ - - - isValid() { - if (this.form instanceof HTMLFormElement) { - return this.form.checkValidity(); - } // If the container is not a valid form, we must check fields individually - - - let validity = true; - this.execOnInputs(input => { - if (input.validity && !input.validity.valid) validity = false; - }, 'all', false); - return validity; - } submitHandler() { var _this$device$postSubm, _this$device; @@ -5950,7 +6016,6 @@ class Form { } if (this.handlerExecuted) return; - if (!this.isValid()) return; const values = this.getValues(); (_this$device$postSubm = (_this$device = this.device).postSubmit) === null || _this$device$postSubm === void 0 ? void 0 : _this$device$postSubm.call(_this$device, values, this); // mark this form as being handled @@ -6139,7 +6204,12 @@ class Form { categorizeInputs() { const selector = this.matching.cssSelector('FORM_INPUTS_SELECTOR'); - this.form.querySelectorAll(selector).forEach(input => this.addInput(input)); + + if (this.form.matches(selector)) { + this.addInput(this.form); + } else { + this.form.querySelectorAll(selector).forEach(input => this.addInput(input)); + } } get submitButtons() { @@ -6152,8 +6222,7 @@ class Form { attemptSubmissionIfNeeded() { if (!this.isLogin || // Only submit login forms - this.submitButtons.length > 1 || // Do not submit if we're unsure about the submit button - !this.isValid() // Do not submit invalid forms + this.submitButtons.length > 1 // Do not submit if we're unsure about the submit button ) return; // check for visible empty fields before attemtping submission // this is to avoid loops where a captcha keeps failing for the user @@ -6202,8 +6271,9 @@ class Form { this.inputs.all.add(input); const opts = { isLogin: this.isLogin, + isHybrid: this.isHybrid, hasCredentials: Boolean((_this$device$settings = this.device.settings.availableInputTypes.credentials) === null || _this$device$settings === void 0 ? void 0 : _this$device$settings.username), - isMobile: this.device.globalConfig.isMobileApp + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities }; this.matching.setInputType(input, this.form, opts); const mainInputType = (0, _matching.getInputMainType)(input); @@ -6447,7 +6517,7 @@ class Form { exports.Form = Form; -},{"../autofill-utils.js":51,"../constants.js":54,"./FormAnalyzer.js":25,"./formatters.js":27,"./inputStyles.js":28,"./inputTypeConfig.js":29,"./matching.js":33}],25:[function(require,module,exports){ +},{"../autofill-utils.js":52,"../constants.js":55,"./FormAnalyzer.js":25,"./formatters.js":27,"./inputStyles.js":28,"./inputTypeConfig.js":29,"./matching.js":33}],25:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -6466,9 +6536,9 @@ var _autofillUtils = require("../autofill-utils.js"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const loginRegex = new RegExp(/sign(ing)?.?in(?!g)|log.?in|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)/i); -const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat|reset) password|password confirm?/i); +const signupRegex = new RegExp(/sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|guest|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|retype|repeat) password|password confirm?/i); const conservativeSignupRegex = new RegExp(/sign.?up|join|register|enroll|newsletter|subscri(be|ption)|settings|preferences|profile|update/i); -const strictSignupRegex = new RegExp(/sign.?up|join|register|enroll|settings|preferences|profile|update/i); +const strictSignupRegex = new RegExp(/sign.?up|join|register|(create|new).+account|enroll|settings|preferences|profile|update/i); class FormAnalyzer { /** @type HTMLElement */ @@ -6498,7 +6568,13 @@ class FormAnalyzer { * @type {string[]} */ - this.signals = []; // Avoid autofill on our signup page + this.signals = []; + /** + * A hybrid form can be either a login or a signup, the site uses a single form for both + * @type {boolean} + */ + + this.isHybrid = false; // Avoid autofill on our signup page if (window.location.href.match(/^https:\/\/(.+\.)?duckduckgo\.com\/email\/choose-address/i)) { return this; @@ -6510,10 +6586,12 @@ class FormAnalyzer { } get isLogin() { + if (this.isHybrid) return false; return this.autofillSignal < 0; } get isSignup() { + if (this.isHybrid) return false; return this.autofillSignal >= 0; } /** @@ -6567,7 +6645,8 @@ class FormAnalyzer { const matchesLogin = string === 'current-password' || loginRegex.test(string); // Check explicitly for unified login/signup forms. They should always be negative, so we increase signal if (shouldCheckUnifiedForm && matchesLogin && strictSignupRegex.test(string)) { - this.decreaseSignalBy(strength + 2, "Unified detected ".concat(signalType)); + this.signals.push("hybrid form: ".concat(signalType)); + this.isHybrid = true; return this; } @@ -6605,28 +6684,26 @@ class FormAnalyzer { this.updateSignal({ string: pageTitle, strength: 2, - signalType: "page title: ".concat(pageTitle) + signalType: "page title: ".concat(pageTitle), + shouldCheckUnifiedForm: true }); } evaluatePageHeadings() { const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [id*="title"]'); - - if (headings) { - headings.forEach(_ref2 => { - let { - textContent - } = _ref2; - textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: "heading: ".concat(textContent), - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }); + headings.forEach(_ref2 => { + let { + textContent + } = _ref2; + textContent = (0, _matching.removeExcessWhitespace)(textContent || ''); + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: "heading: ".concat(textContent), + shouldCheckUnifiedForm: true, + shouldBeConservative: true }); - } + }); } evaluatePage() { @@ -6659,8 +6736,19 @@ class FormAnalyzer { if (el.matches(this.matching.cssSelector('SUBMIT_BUTTON_SELECTOR'))) { - // If we're confident this is a submit button, it's a stronger signal - const strength = (0, _autofillUtils.isLikelyASubmitButton)(el) ? 20 : 2; + // If we're confident this is the submit button, it's a stronger signal + let likelyASubmit = (0, _autofillUtils.isLikelyASubmitButton)(el); + + if (likelyASubmit) { + this.form.querySelectorAll('input[type=submit], button[type=submit]').forEach(submit => { + // If there is another element marked as submit and this is not, flip back to false + if (el.type !== 'submit' && el !== submit) { + likelyASubmit = false; + } + }); + } + + const strength = likelyASubmit ? 20 : 2; this.updateSignal({ string, strength, @@ -6732,7 +6820,7 @@ class FormAnalyzer { var _default = FormAnalyzer; exports.default = _default; -},{"../autofill-utils.js":51,"../constants.js":54,"./matching-configuration.js":32,"./matching.js":33}],26:[function(require,module,exports){ +},{"../autofill-utils.js":52,"../constants.js":55,"./matching-configuration.js":32,"./matching.js":33}],26:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -7841,11 +7929,12 @@ const inputTypeConfig = { shouldDecorate: async (input, _ref4) => { let { isLogin, + isHybrid, device } = _ref4; // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin) { + if (isLogin || isHybrid) { return canBeAutofilled(input, device); } // at this point, it's not a 'login' form, so we could offer to provide a password @@ -7944,7 +8033,7 @@ const isFieldDecorated = input => { exports.isFieldDecorated = isFieldDecorated; -},{"../InputTypes/Credentials.js":36,"../InputTypes/CreditCard.js":37,"../InputTypes/Identity.js":38,"../UI/img/ddgPasswordIcon.js":49,"../constants.js":54,"./logo-svg.js":31,"./matching.js":33}],30:[function(require,module,exports){ +},{"../InputTypes/Credentials.js":36,"../InputTypes/CreditCard.js":37,"../InputTypes/Identity.js":38,"../UI/img/ddgPasswordIcon.js":50,"../constants.js":55,"./logo-svg.js":31,"./matching.js":33}],30:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -8265,8 +8354,8 @@ const matchingConfiguration = { ddgMatcher: { matchers: { email: { - match: '.mail\\b', - skip: 'phone|name|reservation number', + match: '.mail\\b|apple.?id', + skip: 'phone|name|reservation number|code', forceUnknown: 'search|filter|subject|title|\btab\b' }, password: { @@ -8274,7 +8363,7 @@ const matchingConfiguration = { forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { - match: '(user|account|apple|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', + match: '(user|account|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', forceUnknown: 'search|policy' }, // CC @@ -8974,8 +9063,9 @@ class Matching { } if (this.subtypeFromMatchers('email', input)) { - if (opts.isLogin) { - if (!opts.isMobile && !opts.hasCredentials) { + if (opts.isLogin || opts.isHybrid) { + // Show identities when supported and there are no credentials + if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { return 'identities.emailAddress'; } @@ -9001,8 +9091,9 @@ class Matching { /** * @typedef {{ * isLogin?: boolean, + * isHybrid?: boolean, * hasCredentials?: boolean, - * isMobile?: boolean + * supportsIdentitiesAutofill?: boolean * }} SetInputTypeOpts */ @@ -9333,7 +9424,7 @@ class Matching { return false; } - const hasCCSelectorChild = formEl.querySelector(ccFieldSelector); // If the form contains one of the specific selectors, we have high confidence + const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); // If the form contains one of the specific selectors, we have high confidence if (hasCCSelectorChild) return true; // Read form attributes to find a signal @@ -9674,23 +9765,23 @@ function createMatching() { return new Matching(_matchingConfiguration.matchingConfiguration); } -},{"../constants.js":54,"./label-util.js":30,"./matching-configuration.js":32,"./selectors-css.js":34,"./vendor-regex.js":35}],34:[function(require,module,exports){ +},{"../constants.js":55,"./label-util.js":30,"./matching-configuration.js":32,"./selectors-css.js":34,"./vendor-regex.js":35}],34:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.__secret_do_not_use = exports.SUBMIT_BUTTON_SELECTOR = exports.FORM_INPUTS_SELECTOR = void 0; -const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=search]):not([name^=fake i]):not([data-description^=dummy i]),\nselect"; +const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=search]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]),\nselect"; exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; -const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; +const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i]"; const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i]"; @@ -9707,12 +9798,12 @@ const addressProvince = "\n[name=province], [name=state], [autocomplete=address- const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name=birthday_day], [name=birthday-day],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name=birthday_month], [name=birthday-month],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name=birthday_year], [name=birthday-year],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; +const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userID\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later @@ -10384,7 +10475,7 @@ class DefaultScanner { let element = input; // traverse the DOM to search for related inputs - while (element.parentElement && element.parentElement !== document.body) { + while (element.parentElement && element.parentElement !== document.documentElement) { var _element$parentElemen; // If parent includes a form return the current element to avoid overlapping forms @@ -10504,7 +10595,7 @@ function createScanner(device, scannerOptions) { }); } -},{"./Form/Form.js":24,"./Form/matching.js":33,"./Form/selectors-css.js":34,"./autofill-utils.js":51}],41:[function(require,module,exports){ +},{"./Form/Form.js":24,"./Form/matching.js":33,"./Form/selectors-css.js":34,"./autofill-utils.js":52}],41:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -10797,6 +10888,7 @@ _defineProperty(Settings, "defaults", { credentials_saving: false, password_generation: false, emailProtection: false, + emailProtection_incontext_signup: false, inputType_identities: false, inputType_credentials: false, inputType_creditCards: false, @@ -10839,7 +10931,7 @@ _defineProperty(Settings, "defaults", { enabled: null }); -},{"../packages/device-api/index.js":6,"./autofill-utils.js":51,"./deviceApiCalls/__generated__/deviceApiCalls.js":55,"./deviceApiCalls/__generated__/validators.zod.js":56,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],42:[function(require,module,exports){ +},{"../packages/device-api/index.js":6,"./autofill-utils.js":52,"./deviceApiCalls/__generated__/deviceApiCalls.js":56,"./deviceApiCalls/__generated__/validators.zod.js":57,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],42:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -10901,7 +10993,7 @@ class DataHTMLTooltip extends _HTMLTooltip.default { var _default = DataHTMLTooltip; exports.default = _default; -},{"../autofill-utils.js":51,"./HTMLTooltip.js":44}],43:[function(require,module,exports){ +},{"../autofill-utils.js":52,"./HTMLTooltip.js":45}],43:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -10936,11 +11028,14 @@ class EmailHTMLTooltip extends _HTMLTooltip.default { } }; + const firePixel = this.device.firePixel.bind(this.device); this.registerClickableButton(this.usePersonalButton, () => { this.fillForm('personalAddress'); + firePixel('autofill_personal_address'); }); this.registerClickableButton(this.usePrivateButton, () => { this.fillForm('privateAddress'); + firePixel('autofill_private_address'); }); // Get the alias from the extension this.device.getAddresses().then(this.updateAddresses); @@ -10968,7 +11063,39 @@ class EmailHTMLTooltip extends _HTMLTooltip.default { var _default = EmailHTMLTooltip; exports.default = _default; -},{"../autofill-utils.js":51,"./HTMLTooltip.js":44}],44:[function(require,module,exports){ +},{"../autofill-utils.js":52,"./HTMLTooltip.js":45}],44:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _HTMLTooltip = _interopRequireDefault(require("./HTMLTooltip.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +class EmailSignupHTMLTooltip extends _HTMLTooltip.default { + /** + * @param {import("../DeviceInterface/InterfacePrototype").default} device + */ + render(device) { + this.device = device; + this.shadow.innerHTML = "\n".concat(this.options.css, "\n
\n \n
"); + this.tooltip = this.shadow.querySelector('.tooltip'); + this.dismissEmailSignup = this.shadow.querySelector('.js-dismiss-email-signup'); + this.registerClickableButton(this.dismissEmailSignup, () => {// TODO: Persist dismissal + }); + this.init(); + return this; + } + +} + +var _default = EmailSignupHTMLTooltip; +exports.default = _default; + +},{"./HTMLTooltip.js":45}],45:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -11252,7 +11379,7 @@ exports.HTMLTooltip = HTMLTooltip; var _default = HTMLTooltip; exports.default = _default; -},{"../Form/matching.js":33,"../autofill-utils.js":51,"./styles/styles.js":50}],45:[function(require,module,exports){ +},{"../Form/matching.js":33,"../autofill-utils.js":52,"./styles/styles.js":51}],46:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -11266,6 +11393,8 @@ var _DataHTMLTooltip = _interopRequireDefault(require("../DataHTMLTooltip.js")); var _EmailHTMLTooltip = _interopRequireDefault(require("../EmailHTMLTooltip.js")); +var _EmailSignupHTMLTooltop = _interopRequireDefault(require("../EmailSignupHTMLTooltop.js")); + var _HTMLTooltip = require("../HTMLTooltip.js"); var _UIController = require("./UIController.js"); @@ -11276,7 +11405,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope /** * @typedef HTMLTooltipControllerOptions - * @property {"modern" | "legacy"} tooltipKind - A choice between the newer Autofill UI vs the older one used in the extension + * @property {"modern" | "legacy" | "emailsignup"} tooltipKind - A choice between the newer Autofill UI vs the older ones used in the extension * @property {import("../../DeviceInterface/InterfacePrototype").default} device - The device interface that's currently running * regardless of whether this Controller has an open tooltip, or not */ @@ -11355,6 +11484,10 @@ class HTMLTooltipUIController extends _UIController.UIController { if (this._options.tooltipKind === 'legacy') { return new _EmailHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + } + + if (this._options.tooltipKind === 'emailsignup') { + return new _EmailSignupHTMLTooltop.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); } // collect the data for each item to display @@ -11516,7 +11649,7 @@ class HTMLTooltipUIController extends _UIController.UIController { exports.HTMLTooltipUIController = HTMLTooltipUIController; -},{"../../Form/inputTypeConfig.js":29,"../DataHTMLTooltip.js":42,"../EmailHTMLTooltip.js":43,"../HTMLTooltip.js":44,"./UIController.js":48}],46:[function(require,module,exports){ +},{"../../Form/inputTypeConfig.js":29,"../DataHTMLTooltip.js":42,"../EmailHTMLTooltip.js":43,"../EmailSignupHTMLTooltop.js":44,"../HTMLTooltip.js":45,"./UIController.js":49}],47:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -11616,7 +11749,7 @@ class NativeUIController extends _UIController.UIController { exports.NativeUIController = NativeUIController; -},{"../../Form/matching.js":33,"../../deviceApiCalls/__generated__/deviceApiCalls.js":55,"./UIController.js":48}],47:[function(require,module,exports){ +},{"../../Form/matching.js":33,"../../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./UIController.js":49}],48:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -11899,7 +12032,7 @@ class OverlayUIController extends _UIController.UIController { exports.OverlayUIController = OverlayUIController; -},{"../../Form/matching.js":33,"./UIController.js":48}],48:[function(require,module,exports){ +},{"../../Form/matching.js":33,"./UIController.js":49}],49:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -11992,7 +12125,7 @@ class UIController { exports.UIController = UIController; -},{}],49:[function(require,module,exports){ +},{}],50:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -12014,17 +12147,17 @@ exports.ddgCcIconFilled = ddgCcIconFilled; const ddgIdentityIconBase = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4KPHBhdGggeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJub25lIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMiAyMWMyLjE0MyAwIDQuMTExLS43NSA1LjY1Ny0yLS42MjYtLjUwNi0xLjMxOC0uOTI3LTIuMDYtMS4yNS0xLjEtLjQ4LTIuMjg1LS43MzUtMy40ODYtLjc1LTEuMi0uMDE0LTIuMzkyLjIxMS0zLjUwNC42NjQtLjgxNy4zMzMtMS41OC43ODMtMi4yNjQgMS4zMzYgMS41NDYgMS4yNSAzLjUxNCAyIDUuNjU3IDJ6bTQuMzk3LTUuMDgzYy45NjcuNDIyIDEuODY2Ljk4IDIuNjcyIDEuNjU1QzIwLjI3OSAxNi4wMzkgMjEgMTQuMTA0IDIxIDEyYzAtNC45Ny00LjAzLTktOS05cy05IDQuMDMtOSA5YzAgMi4xMDQuNzIyIDQuMDQgMS45MzIgNS41NzIuODc0LS43MzQgMS44Ni0xLjMyOCAyLjkyMS0xLjc2IDEuMzYtLjU1NCAyLjgxNi0uODMgNC4yODMtLjgxMSAxLjQ2Ny4wMTggMi45MTYuMzMgNC4yNi45MTZ6TTEyIDIzYzYuMDc1IDAgMTEtNC45MjUgMTEtMTFTMTguMDc1IDEgMTIgMSAxIDUuOTI1IDEgMTJzNC45MjUgMTEgMTEgMTF6bTMtMTNjMCAxLjY1Ny0xLjM0MyAzLTMgM3MtMy0xLjM0My0zLTMgMS4zNDMtMyAzLTMgMyAxLjM0MyAzIDN6bTIgMGMwIDIuNzYxLTIuMjM5IDUtNSA1cy01LTIuMjM5LTUtNSAyLjIzOS01IDUtNSA1IDIuMjM5IDUgNXoiIGZpbGw9IiMwMDAiLz4KPC9zdmc+Cg=="; exports.ddgIdentityIconBase = ddgIdentityIconBase; -},{}],50:[function(require,module,exports){ +},{}],51:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CSS_STYLES = void 0; -const CSS_STYLES = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n /* move it offscreen to avoid flashing */\n transform: translate(-1000px);\n z-index: 2147483647;\n}\n:not(.top-autofill).wrapper--data {\n font-family: 'SF Pro Text', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 46px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email::before,\n.tooltip--email::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: 20px;\n}\n.tooltip--email::before {\n border-bottom-color: #D0D0D0;\n top: -9px;\n}\n.tooltip--email::after {\n border-bottom-color: #FFFFFF;\n top: -8px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.tooltip__button:hover {\n background-color: rgba(0, 121, 242, 0.9);\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 24px 24px;\n background-repeat: no-repeat;\n background-position: center 4px;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n line-height: 13px;\n}\n.label + .label {\n margin-top: 5px;\n}\n.label.label--medium {\n letter-spacing: -0.08px;\n color: rgba(0,0,0,.9)\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label,\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before {\n /* TODO: use dynamically from src/UI/img/ddgPasswordIcon.js */\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjYzNiA4LjY4MkM5LjYzNiA1LjU0NCAxMi4xOCAzIDE1LjMxOCAzIDE4LjQ1NiAzIDIxIDUuNTQ0IDIxIDguNjgyYzAgMy4xMzgtMi41NDQgNS42ODItNS42ODIgNS42ODItLjY5MiAwLTEuMzUzLS4xMjQtMS45NjQtLjM0OS0uMzcyLS4xMzctLjc5LS4wNDEtMS4wNjYuMjQ1bC0uNzEzLjc0SDEwYy0uNTUyIDAtMSAuNDQ4LTEgMXYySDdjLS41NTIgMC0xIC40NDgtMSAxdjJIM3YtMi44ODFsNi42NjgtNi42NjhjLjI2NS0uMjY2LjM2LS42NTguMjQ0LTEuMDE1LS4xNzktLjU1MS0uMjc2LTEuMTQtLjI3Ni0xLjc1NHpNMTUuMzE4IDFjLTQuMjQyIDAtNy42ODIgMy40NC03LjY4MiA3LjY4MiAwIC42MDcuMDcxIDEuMi4yMDUgMS43NjdsLTYuNTQ4IDYuNTQ4Yy0uMTg4LjE4OC0uMjkzLjQ0Mi0uMjkzLjcwOFYyMmMwIC4yNjUuMTA1LjUyLjI5My43MDcuMTg3LjE4OC40NDIuMjkzLjcwNy4yOTNoNGMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMuMjcyIDAgLjUzMi0uMTEuNzItLjMwNmwuNTc3LS42Yy42NDUuMTc2IDEuMzIzLjI3IDIuMDIxLjI3IDQuMjQzIDAgNy42ODItMy40NCA3LjY4Mi03LjY4MkMyMyA0LjQzOSAxOS41NiAxIDE1LjMxOCAxek0xNSA4YzAtLjU1Mi40NDgtMSAxLTFzMSAuNDQ4IDEgMS0uNDQ4IDEtMSAxLTEtLjQ0OC0xLTF6bTEtM2MtMS42NTcgMC0zIDEuMzQzLTMgM3MxLjM0MyAzIDMgMyAzLTEuMzQzIDMtMy0xLjM0My0zLTMtM3oiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjkiLz4KPC9zdmc+');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 10px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgNDQgNDQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGxpbmVhckdyYWRpZW50IGlkPSJhIj48c3RvcCBvZmZzZXQ9Ii4wMSIgc3RvcC1jb2xvcj0iIzYxNzZiOSIvPjxzdG9wIG9mZnNldD0iLjY5IiBzdG9wLWNvbG9yPSIjMzk0YTlmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTMuOTI5NyIgeDI9IjE3LjA3MiIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNi4zOTgiIHkyPSIxNi4zOTgiLz48bGluZWFyR3JhZGllbnQgaWQ9ImMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMjMuODExNSIgeDI9IjI2LjY3NTIiIHhsaW5rOmhyZWY9IiNhIiB5MT0iMTQuOTY3OSIgeTI9IjE0Ljk2NzkiLz48bWFzayBpZD0iZCIgaGVpZ2h0PSI0MCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgd2lkdGg9IjQwIiB4PSIyIiB5PSIyIj48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0yMi4wMDAzIDQxLjA2NjljMTAuNTMwMiAwIDE5LjA2NjYtOC41MzY0IDE5LjA2NjYtMTkuMDY2NiAwLTEwLjUzMDMtOC41MzY0LTE5LjA2NjcxLTE5LjA2NjYtMTkuMDY2NzEtMTAuNTMwMyAwLTE5LjA2NjcxIDguNTM2NDEtMTkuMDY2NzEgMTkuMDY2NzEgMCAxMC41MzAyIDguNTM2NDEgMTkuMDY2NiAxOS4wNjY3MSAxOS4wNjY2eiIgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9tYXNrPjxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTIyIDQ0YzEyLjE1MDMgMCAyMi05Ljg0OTcgMjItMjIgMC0xMi4xNTAyNi05Ljg0OTctMjItMjItMjItMTIuMTUwMjYgMC0yMiA5Ljg0OTc0LTIyIDIyIDAgMTIuMTUwMyA5Ljg0OTc0IDIyIDIyIDIyeiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PGcgbWFzaz0idXJsKCNkKSI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMjYuMDgxMyA0MS42Mzg2Yy0uOTIwMy0xLjc4OTMtMS44MDAzLTMuNDM1Ni0yLjM0NjYtNC41MjQ2LTEuNDUyLTIuOTA3Ny0yLjkxMTQtNy4wMDctMi4yNDc3LTkuNjUwNy4xMjEtLjQ4MDMtMS4zNjc3LTE3Ljc4Njk5LTIuNDItMTguMzQ0MzItMS4xNjk3LS42MjMzMy0zLjcxMDctMS40NDQ2Ny01LjAyNy0xLjY2NDY3LS45MTY3LS4xNDY2Ni0xLjEyNTcuMTEtMS41MTA3LjE2ODY3LjM2My4wMzY2NyAyLjA5Ljg4NzMzIDIuNDIzNy45MzUtLjMzMzcuMjI3MzMtMS4zMi0uMDA3MzMtMS45NTA3LjI3MTMzLS4zMTkuMTQ2NjctLjU1NzMuNjg5MzQtLjU1Ljk0NiAxLjc5NjctLjE4MzMzIDQuNjA1NC0uMDAzNjYgNi4yNy43MzMyOS0xLjMyMzYuMTUwNC0zLjMzMy4zMTktNC4xOTgzLjc3MzctMi41MDggMS4zMi0zLjYxNTMgNC40MTEtMi45NTUzIDguMTE0My42NTYzIDMuNjk2IDMuNTY0IDE3LjE3ODQgNC40OTE2IDIxLjY4MS45MjQgNC40OTkgMTEuNTUzNyAzLjU1NjcgMTAuMDE3NC41NjF6IiBmaWxsPSIjZDVkN2Q4IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJtMjIuMjg2NSAyNi44NDM5Yy0uNjYgMi42NDM2Ljc5MiA2LjczOTMgMi4yNDc2IDkuNjUwNi40ODkxLjk3MjcgMS4yNDM4IDIuMzkyMSAyLjA1NTggMy45NjM3LTEuODk0LjQ2OTMtNi40ODk1IDEuMTI2NC05LjcxOTEgMC0uOTI0LTQuNDkxNy0zLjgzMTctMTcuOTc3Ny00LjQ5NTMtMjEuNjgxLS42Ni0zLjcwMzMgMC02LjM0NyAyLjUxNTMtNy42NjcuODYxNy0uNDU0NyAyLjA5MzctLjc4NDcgMy40MTM3LS45MzEzLTEuNjY0Ny0uNzQwNy0zLjYzNzQtMS4wMjY3LTUuNDQxNC0uODQzMzYtLjAwNzMtLjc2MjY3IDEuMzM4NC0uNzE4NjcgMS44NDQ0LTEuMDYzMzQtLjMzMzctLjA0NzY2LTEuMTYyNC0uNzk1NjYtMS41MjktLjgzMjMzIDIuMjg4My0uMzkyNDQgNC42NDIzLS4wMjEzOCA2LjY5OSAxLjA1NiAxLjA0ODYuNTYxIDEuNzg5MyAxLjE2MjMzIDIuMjQ3NiAxLjc5MzAzIDEuMTk1NC4yMjczIDIuMjUxNC42NiAyLjk0MDcgMS4zNDkzIDIuMTE5MyAyLjExNTcgNC4wMTEzIDYuOTUyIDMuMjE5MyA5LjczMTMtLjIyMzYuNzctLjczMzMgMS4zMzEtMS4zNzEzIDEuNzk2Ny0xLjIzOTMuOTAyLTEuMDE5My0xLjA0NS00LjEwMy45NzE3LS4zOTk3LjI2MDMtLjM5OTcgMi4yMjU2LS41MjQzIDIuNzA2eiIgZmlsbD0iI2ZmZiIvPjwvZz48ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PHBhdGggZD0ibTE2LjY3MjQgMjAuMzU0Yy43Njc1IDAgMS4zODk2LS42MjIxIDEuMzg5Ni0xLjM4OTZzLS42MjIxLTEuMzg5Ny0xLjM4OTYtMS4zODk3LTEuMzg5Ny42MjIyLTEuMzg5NyAxLjM4OTcuNjIyMiAxLjM4OTYgMS4zODk3IDEuMzg5NnoiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMTcuMjkyNCAxOC44NjE3Yy4xOTg1IDAgLjM1OTQtLjE2MDguMzU5NC0uMzU5M3MtLjE2MDktLjM1OTMtLjM1OTQtLjM1OTNjLS4xOTg0IDAtLjM1OTMuMTYwOC0uMzU5My4zNTkzcy4xNjA5LjM1OTMuMzU5My4zNTkzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im0yNS45NTY4IDE5LjMzMTFjLjY1ODEgMCAxLjE5MTctLjUzMzUgMS4xOTE3LTEuMTkxNyAwLS42NTgxLS41MzM2LTEuMTkxNi0xLjE5MTctMS4xOTE2cy0xLjE5MTcuNTMzNS0xLjE5MTcgMS4xOTE2YzAgLjY1ODIuNTMzNiAxLjE5MTcgMS4xOTE3IDEuMTkxN3oiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMjYuNDg4MiAxOC4wNTExYy4xNzAxIDAgLjMwOC0uMTM3OS4zMDgtLjMwOHMtLjEzNzktLjMwOC0uMzA4LS4zMDgtLjMwOC4xMzc5LS4zMDguMzA4LjEzNzkuMzA4LjMwOC4zMDh6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0ibTE3LjA3MiAxNC45NDJzLTEuMDQ4Ni0uNDc2Ni0yLjA2NDMuMTY1Yy0xLjAxNTcuNjM4LS45NzkgMS4yOTA3LS45NzkgMS4yOTA3cy0uNTM5LTEuMjAyNy44OTgzLTEuNzkzYzEuNDQxLS41ODY3IDIuMTQ1LjMzNzMgMi4xNDUuMzM3M3oiIGZpbGw9InVybCgjYikiLz48cGF0aCBkPSJtMjYuNjc1MiAxNC44NDY3cy0uNzUxNy0uNDI5LTEuMzM4My0uNDIxN2MtMS4xOTkuMDE0Ny0xLjUyNTQuNTQyNy0xLjUyNTQuNTQyN3MuMjAxNy0xLjI2MTQgMS43MzQ0LTEuMDA4NGMuNDk5Ny4wOTE0LjkyMjMuNDIzNCAxLjEyOTMuODg3NHoiIGZpbGw9InVybCgjYykiLz48cGF0aCBkPSJtMjAuOTI1OCAyNC4zMjFjLjEzOTMtLjg0MzMgMi4zMS0yLjQzMSAzLjg1LTIuNTMgMS41NC0uMDk1MyAyLjAxNjctLjA3MzMgMy4zLS4zODEzIDEuMjg3LS4zMDQzIDQuNTk4LTEuMTI5MyA1LjUxMS0xLjU1NDcuOTE2Ny0uNDIxNiA0LjgwMzMuMjA5IDIuMDY0MyAxLjczOC0xLjE4NDMuNjYzNy00LjM3OCAxLjg4MS02LjY2MjMgMi41NjMtMi4yODA3LjY4Mi0zLjY2My0uNjUyNi00LjQyMi40Njk0LS42MDEzLjg5MS0uMTIxIDIuMTEyIDIuNjAzMyAyLjM2NSAzLjY4MTQuMzQxIDcuMjA4Ny0xLjY1NzQgNy41OTc0LS41OTQuMzg4NiAxLjA2MzMtMy4xNjA3IDIuMzgzMy01LjMyNCAyLjQyNzMtMi4xNjM0LjA0MDMtNi41MTk0LTEuNDMtNy4xNzItMS44ODQ3LS42NTY0LS40NTEtMS41MjU0LTEuNTE0My0xLjM0NTctMi42MTh6IiBmaWxsPSIjZmRkMjBhIi8+PHBhdGggZD0ibTI4Ljg4MjUgMzEuODM4NmMtLjc3NzMtLjE3MjQtNC4zMTIgMi41MDA2LTQuMzEyIDIuNTAwNmguMDAzN2wtLjE2NSAyLjA1MzRzNC4wNDA2IDEuNjUzNiA0LjczIDEuMzk3Yy42ODkzLS4yNjQuNTE3LTUuNzc1LS4yNTY3LTUuOTUxem0tMTEuNTQ2MyAxLjAzNGMuMDg0My0xLjExODQgNS4yNTQzIDEuNjQyNiA1LjI1NDMgMS42NDI2bC4wMDM3LS4wMDM2LjI1NjYgMi4xNTZzLTQuMzA4MyAyLjU4MTMtNC45MTMzIDIuMjM2NmMtLjYwMTMtLjM0NDYtLjY4OTMtNC45MDk2LS42MDEzLTYuMDMxNnoiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjEuMzQgMzQuODA0OWMwIDEuODA3Ny0uMjYwNCAyLjU4NS41MTMzIDIuNzU3NC43NzczLjE3MjMgMi4yNDAzIDAgMi43NjEtLjM0NDcuNTEzMy0uMzQ0Ny4wODQzLTIuNjY5My0uMDg4LTMuMTAycy0zLjE5LS4wODgtMy4xOS42ODkzeiIgZmlsbD0iIzQzYTI0NCIvPjxwYXRoIGQ9Im0yMS42NzAxIDM0LjQwNTFjMCAxLjgwNzYtLjI2MDQgMi41ODEzLjUxMzMgMi43NTM2Ljc3MzcuMTc2IDIuMjM2NyAwIDIuNzU3My0uMzQ0Ni41MTctLjM0NDcuMDg4LTIuNjY5NC0uMDg0My0zLjEwMi0uMTcyMy0uNDMyNy0zLjE5LS4wODQ0LTMuMTkuNjg5M3oiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjIuMDAwMiA0MC40NDgxYzEwLjE4ODUgMCAxOC40NDc5LTguMjU5NCAxOC40NDc5LTE4LjQ0NzlzLTguMjU5NC0xOC40NDc5NS0xOC40NDc5LTE4LjQ0Nzk1LTE4LjQ0Nzk1IDguMjU5NDUtMTguNDQ3OTUgMTguNDQ3OTUgOC4yNTk0NSAxOC40NDc5IDE4LjQ0Nzk1IDE4LjQ0Nzl6bTAgMS43MTg3YzExLjEzNzcgMCAyMC4xNjY2LTkuMDI4OSAyMC4xNjY2LTIwLjE2NjYgMC0xMS4xMzc4LTkuMDI4OS0yMC4xNjY3LTIwLjE2NjYtMjAuMTY2Ny0xMS4xMzc4IDAtMjAuMTY2NyA5LjAyODktMjAuMTY2NyAyMC4xNjY3IDAgMTEuMTM3NyA5LjAyODkgMjAuMTY2NiAyMC4xNjY3IDIwLjE2NjZ6IiBmaWxsPSIjZmZmIi8+PC9nPjwvc3ZnPg==');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n"; +const CSS_STYLES = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n /* move it offscreen to avoid flashing */\n transform: translate(-1000px);\n z-index: 2147483647;\n}\n:not(.top-autofill).wrapper--data {\n font-family: 'SF Pro Text', -apple-system,\n BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 46px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email::before,\n.tooltip--email::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: 20px;\n}\n.tooltip--email::before {\n border-bottom-color: #D0D0D0;\n top: -9px;\n}\n.tooltip--email::after {\n border-bottom-color: #FFFFFF;\n top: -8px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.tooltip__button:hover {\n background-color: rgba(0, 121, 242, 0.9);\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 24px 24px;\n background-repeat: no-repeat;\n background-position: center 4px;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n line-height: 13px;\n}\n.label + .label {\n margin-top: 5px;\n}\n.label.label--medium {\n letter-spacing: -0.08px;\n color: rgba(0,0,0,.9)\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label,\n.tooltip__button.currentFocus .label,\n.tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before {\n /* TODO: use dynamically from src/UI/img/ddgPasswordIcon.js */\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjYzNiA4LjY4MkM5LjYzNiA1LjU0NCAxMi4xOCAzIDE1LjMxOCAzIDE4LjQ1NiAzIDIxIDUuNTQ0IDIxIDguNjgyYzAgMy4xMzgtMi41NDQgNS42ODItNS42ODIgNS42ODItLjY5MiAwLTEuMzUzLS4xMjQtMS45NjQtLjM0OS0uMzcyLS4xMzctLjc5LS4wNDEtMS4wNjYuMjQ1bC0uNzEzLjc0SDEwYy0uNTUyIDAtMSAuNDQ4LTEgMXYySDdjLS41NTIgMC0xIC40NDgtMSAxdjJIM3YtMi44ODFsNi42NjgtNi42NjhjLjI2NS0uMjY2LjM2LS42NTguMjQ0LTEuMDE1LS4xNzktLjU1MS0uMjc2LTEuMTQtLjI3Ni0xLjc1NHpNMTUuMzE4IDFjLTQuMjQyIDAtNy42ODIgMy40NC03LjY4MiA3LjY4MiAwIC42MDcuMDcxIDEuMi4yMDUgMS43NjdsLTYuNTQ4IDYuNTQ4Yy0uMTg4LjE4OC0uMjkzLjQ0Mi0uMjkzLjcwOFYyMmMwIC4yNjUuMTA1LjUyLjI5My43MDcuMTg3LjE4OC40NDIuMjkzLjcwNy4yOTNoNGMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMxLjEwNSAwIDItLjg5NSAyLTJ2LTFoMWMuMjcyIDAgLjUzMi0uMTEuNzItLjMwNmwuNTc3LS42Yy42NDUuMTc2IDEuMzIzLjI3IDIuMDIxLjI3IDQuMjQzIDAgNy42ODItMy40NCA3LjY4Mi03LjY4MkMyMyA0LjQzOSAxOS41NiAxIDE1LjMxOCAxek0xNSA4YzAtLjU1Mi40NDgtMSAxLTFzMSAuNDQ4IDEgMS0uNDQ4IDEtMSAxLTEtLjQ0OC0xLTF6bTEtM2MtMS42NTcgMC0zIDEuMzQzLTMgM3MxLjM0MyAzIDMgMyAzLTEuMzQzIDMtMy0xLjM0My0zLTMtM3oiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjkiLz4KPC9zdmc+');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 10px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgNDQgNDQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGxpbmVhckdyYWRpZW50IGlkPSJhIj48c3RvcCBvZmZzZXQ9Ii4wMSIgc3RvcC1jb2xvcj0iIzYxNzZiOSIvPjxzdG9wIG9mZnNldD0iLjY5IiBzdG9wLWNvbG9yPSIjMzk0YTlmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTMuOTI5NyIgeDI9IjE3LjA3MiIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNi4zOTgiIHkyPSIxNi4zOTgiLz48bGluZWFyR3JhZGllbnQgaWQ9ImMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMjMuODExNSIgeDI9IjI2LjY3NTIiIHhsaW5rOmhyZWY9IiNhIiB5MT0iMTQuOTY3OSIgeTI9IjE0Ljk2NzkiLz48bWFzayBpZD0iZCIgaGVpZ2h0PSI0MCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgd2lkdGg9IjQwIiB4PSIyIiB5PSIyIj48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0yMi4wMDAzIDQxLjA2NjljMTAuNTMwMiAwIDE5LjA2NjYtOC41MzY0IDE5LjA2NjYtMTkuMDY2NiAwLTEwLjUzMDMtOC41MzY0LTE5LjA2NjcxLTE5LjA2NjYtMTkuMDY2NzEtMTAuNTMwMyAwLTE5LjA2NjcxIDguNTM2NDEtMTkuMDY2NzEgMTkuMDY2NzEgMCAxMC41MzAyIDguNTM2NDEgMTkuMDY2NiAxOS4wNjY3MSAxOS4wNjY2eiIgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9tYXNrPjxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTIyIDQ0YzEyLjE1MDMgMCAyMi05Ljg0OTcgMjItMjIgMC0xMi4xNTAyNi05Ljg0OTctMjItMjItMjItMTIuMTUwMjYgMC0yMiA5Ljg0OTc0LTIyIDIyIDAgMTIuMTUwMyA5Ljg0OTc0IDIyIDIyIDIyeiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PGcgbWFzaz0idXJsKCNkKSI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMjYuMDgxMyA0MS42Mzg2Yy0uOTIwMy0xLjc4OTMtMS44MDAzLTMuNDM1Ni0yLjM0NjYtNC41MjQ2LTEuNDUyLTIuOTA3Ny0yLjkxMTQtNy4wMDctMi4yNDc3LTkuNjUwNy4xMjEtLjQ4MDMtMS4zNjc3LTE3Ljc4Njk5LTIuNDItMTguMzQ0MzItMS4xNjk3LS42MjMzMy0zLjcxMDctMS40NDQ2Ny01LjAyNy0xLjY2NDY3LS45MTY3LS4xNDY2Ni0xLjEyNTcuMTEtMS41MTA3LjE2ODY3LjM2My4wMzY2NyAyLjA5Ljg4NzMzIDIuNDIzNy45MzUtLjMzMzcuMjI3MzMtMS4zMi0uMDA3MzMtMS45NTA3LjI3MTMzLS4zMTkuMTQ2NjctLjU1NzMuNjg5MzQtLjU1Ljk0NiAxLjc5NjctLjE4MzMzIDQuNjA1NC0uMDAzNjYgNi4yNy43MzMyOS0xLjMyMzYuMTUwNC0zLjMzMy4zMTktNC4xOTgzLjc3MzctMi41MDggMS4zMi0zLjYxNTMgNC40MTEtMi45NTUzIDguMTE0My42NTYzIDMuNjk2IDMuNTY0IDE3LjE3ODQgNC40OTE2IDIxLjY4MS45MjQgNC40OTkgMTEuNTUzNyAzLjU1NjcgMTAuMDE3NC41NjF6IiBmaWxsPSIjZDVkN2Q4IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJtMjIuMjg2NSAyNi44NDM5Yy0uNjYgMi42NDM2Ljc5MiA2LjczOTMgMi4yNDc2IDkuNjUwNi40ODkxLjk3MjcgMS4yNDM4IDIuMzkyMSAyLjA1NTggMy45NjM3LTEuODk0LjQ2OTMtNi40ODk1IDEuMTI2NC05LjcxOTEgMC0uOTI0LTQuNDkxNy0zLjgzMTctMTcuOTc3Ny00LjQ5NTMtMjEuNjgxLS42Ni0zLjcwMzMgMC02LjM0NyAyLjUxNTMtNy42NjcuODYxNy0uNDU0NyAyLjA5MzctLjc4NDcgMy40MTM3LS45MzEzLTEuNjY0Ny0uNzQwNy0zLjYzNzQtMS4wMjY3LTUuNDQxNC0uODQzMzYtLjAwNzMtLjc2MjY3IDEuMzM4NC0uNzE4NjcgMS44NDQ0LTEuMDYzMzQtLjMzMzctLjA0NzY2LTEuMTYyNC0uNzk1NjYtMS41MjktLjgzMjMzIDIuMjg4My0uMzkyNDQgNC42NDIzLS4wMjEzOCA2LjY5OSAxLjA1NiAxLjA0ODYuNTYxIDEuNzg5MyAxLjE2MjMzIDIuMjQ3NiAxLjc5MzAzIDEuMTk1NC4yMjczIDIuMjUxNC42NiAyLjk0MDcgMS4zNDkzIDIuMTE5MyAyLjExNTcgNC4wMTEzIDYuOTUyIDMuMjE5MyA5LjczMTMtLjIyMzYuNzctLjczMzMgMS4zMzEtMS4zNzEzIDEuNzk2Ny0xLjIzOTMuOTAyLTEuMDE5My0xLjA0NS00LjEwMy45NzE3LS4zOTk3LjI2MDMtLjM5OTcgMi4yMjU2LS41MjQzIDIuNzA2eiIgZmlsbD0iI2ZmZiIvPjwvZz48ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PHBhdGggZD0ibTE2LjY3MjQgMjAuMzU0Yy43Njc1IDAgMS4zODk2LS42MjIxIDEuMzg5Ni0xLjM4OTZzLS42MjIxLTEuMzg5Ny0xLjM4OTYtMS4zODk3LTEuMzg5Ny42MjIyLTEuMzg5NyAxLjM4OTcuNjIyMiAxLjM4OTYgMS4zODk3IDEuMzg5NnoiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMTcuMjkyNCAxOC44NjE3Yy4xOTg1IDAgLjM1OTQtLjE2MDguMzU5NC0uMzU5M3MtLjE2MDktLjM1OTMtLjM1OTQtLjM1OTNjLS4xOTg0IDAtLjM1OTMuMTYwOC0uMzU5My4zNTkzcy4xNjA5LjM1OTMuMzU5My4zNTkzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im0yNS45NTY4IDE5LjMzMTFjLjY1ODEgMCAxLjE5MTctLjUzMzUgMS4xOTE3LTEuMTkxNyAwLS42NTgxLS41MzM2LTEuMTkxNi0xLjE5MTctMS4xOTE2cy0xLjE5MTcuNTMzNS0xLjE5MTcgMS4xOTE2YzAgLjY1ODIuNTMzNiAxLjE5MTcgMS4xOTE3IDEuMTkxN3oiIGZpbGw9IiMyZDRmOGUiLz48cGF0aCBkPSJtMjYuNDg4MiAxOC4wNTExYy4xNzAxIDAgLjMwOC0uMTM3OS4zMDgtLjMwOHMtLjEzNzktLjMwOC0uMzA4LS4zMDgtLjMwOC4xMzc5LS4zMDguMzA4LjEzNzkuMzA4LjMwOC4zMDh6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0ibTE3LjA3MiAxNC45NDJzLTEuMDQ4Ni0uNDc2Ni0yLjA2NDMuMTY1Yy0xLjAxNTcuNjM4LS45NzkgMS4yOTA3LS45NzkgMS4yOTA3cy0uNTM5LTEuMjAyNy44OTgzLTEuNzkzYzEuNDQxLS41ODY3IDIuMTQ1LjMzNzMgMi4xNDUuMzM3M3oiIGZpbGw9InVybCgjYikiLz48cGF0aCBkPSJtMjYuNjc1MiAxNC44NDY3cy0uNzUxNy0uNDI5LTEuMzM4My0uNDIxN2MtMS4xOTkuMDE0Ny0xLjUyNTQuNTQyNy0xLjUyNTQuNTQyN3MuMjAxNy0xLjI2MTQgMS43MzQ0LTEuMDA4NGMuNDk5Ny4wOTE0LjkyMjMuNDIzNCAxLjEyOTMuODg3NHoiIGZpbGw9InVybCgjYykiLz48cGF0aCBkPSJtMjAuOTI1OCAyNC4zMjFjLjEzOTMtLjg0MzMgMi4zMS0yLjQzMSAzLjg1LTIuNTMgMS41NC0uMDk1MyAyLjAxNjctLjA3MzMgMy4zLS4zODEzIDEuMjg3LS4zMDQzIDQuNTk4LTEuMTI5MyA1LjUxMS0xLjU1NDcuOTE2Ny0uNDIxNiA0LjgwMzMuMjA5IDIuMDY0MyAxLjczOC0xLjE4NDMuNjYzNy00LjM3OCAxLjg4MS02LjY2MjMgMi41NjMtMi4yODA3LjY4Mi0zLjY2My0uNjUyNi00LjQyMi40Njk0LS42MDEzLjg5MS0uMTIxIDIuMTEyIDIuNjAzMyAyLjM2NSAzLjY4MTQuMzQxIDcuMjA4Ny0xLjY1NzQgNy41OTc0LS41OTQuMzg4NiAxLjA2MzMtMy4xNjA3IDIuMzgzMy01LjMyNCAyLjQyNzMtMi4xNjM0LjA0MDMtNi41MTk0LTEuNDMtNy4xNzItMS44ODQ3LS42NTY0LS40NTEtMS41MjU0LTEuNTE0My0xLjM0NTctMi42MTh6IiBmaWxsPSIjZmRkMjBhIi8+PHBhdGggZD0ibTI4Ljg4MjUgMzEuODM4NmMtLjc3NzMtLjE3MjQtNC4zMTIgMi41MDA2LTQuMzEyIDIuNTAwNmguMDAzN2wtLjE2NSAyLjA1MzRzNC4wNDA2IDEuNjUzNiA0LjczIDEuMzk3Yy42ODkzLS4yNjQuNTE3LTUuNzc1LS4yNTY3LTUuOTUxem0tMTEuNTQ2MyAxLjAzNGMuMDg0My0xLjExODQgNS4yNTQzIDEuNjQyNiA1LjI1NDMgMS42NDI2bC4wMDM3LS4wMDM2LjI1NjYgMi4xNTZzLTQuMzA4MyAyLjU4MTMtNC45MTMzIDIuMjM2NmMtLjYwMTMtLjM0NDYtLjY4OTMtNC45MDk2LS42MDEzLTYuMDMxNnoiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjEuMzQgMzQuODA0OWMwIDEuODA3Ny0uMjYwNCAyLjU4NS41MTMzIDIuNzU3NC43NzczLjE3MjMgMi4yNDAzIDAgMi43NjEtLjM0NDcuNTEzMy0uMzQ0Ny4wODQzLTIuNjY5My0uMDg4LTMuMTAycy0zLjE5LS4wODgtMy4xOS42ODkzeiIgZmlsbD0iIzQzYTI0NCIvPjxwYXRoIGQ9Im0yMS42NzAxIDM0LjQwNTFjMCAxLjgwNzYtLjI2MDQgMi41ODEzLjUxMzMgMi43NTM2Ljc3MzcuMTc2IDIuMjM2NyAwIDIuNzU3My0uMzQ0Ni41MTctLjM0NDcuMDg4LTIuNjY5NC0uMDg0My0zLjEwMi0uMTcyMy0uNDMyNy0zLjE5LS4wODQ0LTMuMTkuNjg5M3oiIGZpbGw9IiM2NWJjNDYiLz48cGF0aCBkPSJtMjIuMDAwMiA0MC40NDgxYzEwLjE4ODUgMCAxOC40NDc5LTguMjU5NCAxOC40NDc5LTE4LjQ0NzlzLTguMjU5NC0xOC40NDc5NS0xOC40NDc5LTE4LjQ0Nzk1LTE4LjQ0Nzk1IDguMjU5NDUtMTguNDQ3OTUgMTguNDQ3OTUgOC4yNTk0NSAxOC40NDc5IDE4LjQ0Nzk1IDE4LjQ0Nzl6bTAgMS43MTg3YzExLjEzNzcgMCAyMC4xNjY2LTkuMDI4OSAyMC4xNjY2LTIwLjE2NjYgMC0xMS4xMzc4LTkuMDI4OS0yMC4xNjY3LTIwLjE2NjYtMjAuMTY2Ny0xMS4xMzc4IDAtMjAuMTY2NyA5LjAyODktMjAuMTY2NyAyMC4xNjY3IDAgMTEuMTM3NyA5LjAyODkgMjAuMTY2NiAyMC4xNjY3IDIwLjE2NjZ6IiBmaWxsPSIjZmZmIi8+PC9nPjwvc3ZnPg==');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n\n/* Email Protection signup notice */\n:not(.top-autofill) .tooltip--email-signup {\n color: #222222;\n padding: 20px;\n width: 380px;\n}\n\n.tooltip--email-signup h1 {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.5;\n margin: 0;\n}\n\n.tooltip--email-signup p {\n font-weight: 400;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.notice-controls {\n display: flex;\n}\n\n.tooltip--email-signup .notice-controls > * {\n border-radius: 8px;\n border: 0;\n cursor: pointer;\n display: inline-block;\n font-style: normal;\n font-weight: bold;\n padding: 8px 12px;\n text-decoration: none;\n}\n\n.notice-controls .ghost {\n margin-left: 1rem;\n}\n\n.tooltip--email-signup a.primary {\n background: #3969EF;\n color: #fff;\n}\n\n.tooltip--email-signup a.primary:hover,\n.tooltip--email-signup a.primary:focus {\n background: #2b55ca;\n}\n\n.tooltip--email-signup a.primary:active {\n background: #1e42a4;\n}\n\n.tooltip--email-signup button.ghost {\n background: transparent;\n color: #3969EF;\n}\n\n.tooltip--email-signup button.ghost:hover,\n.tooltip--email-signup button.ghost:focus {\n background-color: rgba(0, 0, 0, 0.06);\n color: #2b55ca;\n}\n\n.tooltip--email-signup button.ghost:active {\n background-color: rgba(0, 0, 0, 0.12);\n color: #1e42a4;\n}"; exports.CSS_STYLES = CSS_STYLES; -},{}],51:[function(require,module,exports){ +},{}],52:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -12182,7 +12315,9 @@ const fireEventsOnSelect = el => { const setValueForSelect = (el, val) => { const subtype = (0, _matching.getInputSubtype)(el); const isMonth = subtype.includes('Month'); - const isZeroBasedNumber = isMonth && el.options[0].value === '0' && el.options.length === 12; // Loop first through all values because they tend to be more precise + const isZeroBasedNumber = isMonth && el.options[0].value === '0' && el.options.length === 12; + const stringVal = String(val); + const numberVal = Number(val); // Loop first through all values because they tend to be more precise for (const option of el.options) { // If values for months are zero-based (Jan === 0), add one to match our data type @@ -12194,7 +12329,7 @@ const setValueForSelect = (el, val) => { // TODO: implement alternative versions of values (abbreviations for States/Provinces or variations like USA, US, United States, etc.) - if (value === String(val)) { + if (value === stringVal || Number(value) === numberVal) { if (option.selected) return false; option.selected = true; fireEventsOnSelect(el); @@ -12203,7 +12338,7 @@ const setValueForSelect = (el, val) => { } for (const option of el.options) { - if (option.innerText === String(val)) { + if (option.innerText === stringVal || Number(option.innerText) === numberVal) { if (option.selected) return false; option.selected = true; fireEventsOnSelect(el); @@ -12450,7 +12585,7 @@ const getText = el => { exports.getText = getText; -},{"./Form/matching.js":33}],52:[function(require,module,exports){ +},{"./Form/matching.js":33}],53:[function(require,module,exports){ "use strict"; require("./requestIdleCallback.js"); @@ -12479,7 +12614,7 @@ var _DeviceInterface = require("./DeviceInterface.js"); } })(); -},{"./DeviceInterface.js":14,"./requestIdleCallback.js":63}],53:[function(require,module,exports){ +},{"./DeviceInterface.js":14,"./requestIdleCallback.js":64}],54:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -12565,7 +12700,7 @@ function createGlobalConfig(overrides) { return config; } -},{}],54:[function(require,module,exports){ +},{}],55:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -12579,7 +12714,7 @@ const constants = { }; exports.constants = constants; -},{}],55:[function(require,module,exports){ +},{}],56:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -12812,13 +12947,13 @@ class SendJSPixelCall extends _deviceApi.DeviceApiCall { exports.SendJSPixelCall = SendJSPixelCall; -},{"../../../packages/device-api":6,"./validators.zod.js":56}],56:[function(require,module,exports){ +},{"../../../packages/device-api":6,"./validators.zod.js":57}],57:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.userPreferencesSchema = exports.triggerContextSchema = exports.storeFormDataSchema = exports.setSizeParamsSchema = exports.sendJSPixelParamsSchema = exports.selectedDetailParamsSchema = exports.runtimeConfigurationSchema = exports.providerStatusUpdatedSchema = exports.outgoingCredentialsSchema = exports.getRuntimeConfigurationResponseSchema = exports.getAvailableInputTypesResultSchema = exports.getAutofillInitDataResponseSchema = exports.getAutofillDataResponseSchema = exports.getAutofillDataRequestSchema = exports.getAutofillCredentialsResultSchema = exports.getAutofillCredentialsParamsSchema = exports.getAliasResultSchema = exports.getAliasParamsSchema = exports.genericErrorSchema = exports.credentialsSchema = exports.contentScopeSchema = exports.contentScopeFeaturesSchema = exports.contentScopeFeaturesItemSettingsSchema = exports.checkCredentialsProviderStatusResultSchema = exports.availableInputTypesSchema = exports.autofillSettingsSchema = exports.autofillFeatureTogglesSchema = exports.askToUnlockProviderResultSchema = void 0; +exports.userPreferencesSchema = exports.triggerContextSchema = exports.storeFormDataSchema = exports.setSizeParamsSchema = exports.sendJSPixelParamsSchema = exports.selectedDetailParamsSchema = exports.runtimeConfigurationSchema = exports.providerStatusUpdatedSchema = exports.outgoingCredentialsSchema = exports.getRuntimeConfigurationResponseSchema = exports.getAvailableInputTypesResultSchema = exports.getAutofillInitDataResponseSchema = exports.getAutofillDataResponseSchema = exports.getAutofillDataRequestSchema = exports.getAutofillCredentialsResultSchema = exports.getAutofillCredentialsParamsSchema = exports.getAliasResultSchema = exports.getAliasParamsSchema = exports.genericErrorSchema = exports.credentialsSchema = exports.contentScopeSchema = exports.checkCredentialsProviderStatusResultSchema = exports.availableInputTypesSchema = exports.autofillSettingsSchema = exports.autofillFeatureTogglesSchema = exports.askToUnlockProviderResultSchema = void 0; const credentialsSchema = null; exports.credentialsSchema = credentialsSchema; const availableInputTypesSchema = null; @@ -12845,12 +12980,12 @@ const getAutofillInitDataResponseSchema = null; exports.getAutofillInitDataResponseSchema = getAutofillInitDataResponseSchema; const getAvailableInputTypesResultSchema = null; exports.getAvailableInputTypesResultSchema = getAvailableInputTypesResultSchema; -const contentScopeFeaturesItemSettingsSchema = null; -exports.contentScopeFeaturesItemSettingsSchema = contentScopeFeaturesItemSettingsSchema; +const contentScopeSchema = null; +exports.contentScopeSchema = contentScopeSchema; const userPreferencesSchema = null; exports.userPreferencesSchema = userPreferencesSchema; -const contentScopeFeaturesSchema = null; -exports.contentScopeFeaturesSchema = contentScopeFeaturesSchema; +const runtimeConfigurationSchema = null; +exports.runtimeConfigurationSchema = runtimeConfigurationSchema; const selectedDetailParamsSchema = null; exports.selectedDetailParamsSchema = selectedDetailParamsSchema; const sendJSPixelParamsSchema = null; @@ -12867,16 +13002,12 @@ const checkCredentialsProviderStatusResultSchema = null; exports.checkCredentialsProviderStatusResultSchema = checkCredentialsProviderStatusResultSchema; const getAutofillDataRequestSchema = null; exports.getAutofillDataRequestSchema = getAutofillDataRequestSchema; -const contentScopeSchema = null; -exports.contentScopeSchema = contentScopeSchema; -const runtimeConfigurationSchema = null; -exports.runtimeConfigurationSchema = runtimeConfigurationSchema; -const storeFormDataSchema = null; -exports.storeFormDataSchema = storeFormDataSchema; const getRuntimeConfigurationResponseSchema = null; exports.getRuntimeConfigurationResponseSchema = getRuntimeConfigurationResponseSchema; +const storeFormDataSchema = null; +exports.storeFormDataSchema = storeFormDataSchema; -},{}],57:[function(require,module,exports){ +},{}],58:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -12917,7 +13048,7 @@ class GetAlias extends _index.DeviceApiCall { exports.GetAlias = GetAlias; -},{"../../packages/device-api/index.js":6,"./__generated__/validators.zod.js":56}],58:[function(require,module,exports){ +},{"../../packages/device-api/index.js":6,"./__generated__/validators.zod.js":57}],59:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -13075,7 +13206,7 @@ function androidSpecificAvailableInputTypes(globalConfig) { }; } -},{"../../../packages/device-api/index.js":6,"../__generated__/deviceApiCalls.js":55}],59:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":6,"../__generated__/deviceApiCalls.js":56}],60:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -13151,7 +13282,7 @@ function appleSpecificRuntimeConfiguration(globalConfig) { }; } -},{"../../../packages/device-api/index.js":6,"../__generated__/deviceApiCalls.js":55,"@duckduckgo/content-scope-utils":2}],60:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":6,"../__generated__/deviceApiCalls.js":56,"@duckduckgo/content-scope-utils":2}],61:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -13181,6 +13312,11 @@ class ExtensionTransport extends _index.DeviceApiTransport { if (deviceApiCall instanceof _deviceApiCalls.GetAvailableInputTypesCall) { return deviceApiCall.result(await extensionSpecificGetAvailableInputTypes()); + } // TODO: unify all calls to use deviceApiCall.method instead of all these if blocks + + + if (deviceApiCall instanceof _deviceApiCalls.SendJSPixelCall) { + return deviceApiCall.result(await extensionSpecificSendPixel(deviceApiCall.params.pixelName)); } throw new Error('not implemented yet for ' + deviceApiCall.method); @@ -13242,8 +13378,25 @@ async function getContentScopeConfig() { }); }); } +/** + * @param {import('../__generated__/validators-ts').SendJSPixelParams['pixelName']} pixelName + */ + + +async function extensionSpecificSendPixel(pixelName) { + return new Promise(resolve => { + chrome.runtime.sendMessage({ + messageType: 'sendJSPixel', + options: { + pixelName + } + }, () => { + resolve(true); + }); + }); +} -},{"../../../packages/device-api/index.js":6,"../../Settings.js":41,"../../autofill-utils.js":51,"../__generated__/deviceApiCalls.js":55}],61:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":6,"../../Settings.js":41,"../../autofill-utils.js":52,"../__generated__/deviceApiCalls.js":56}],62:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -13297,7 +13450,7 @@ function createTransport(globalConfig) { return new _extensionTransport.ExtensionTransport(globalConfig); } -},{"./android.transport.js":58,"./apple.transport.js":59,"./extension.transport.js":60,"./windows.transport.js":62}],62:[function(require,module,exports){ +},{"./android.transport.js":59,"./apple.transport.js":60,"./extension.transport.js":61,"./windows.transport.js":63}],63:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -13397,7 +13550,7 @@ function waitForWindowsResponse(responseId, options) { }); } -},{"../../../packages/device-api/index.js":6}],63:[function(require,module,exports){ +},{"../../../packages/device-api/index.js":6}],64:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -13445,4 +13598,4 @@ window.cancelIdleCallback = window.cancelIdleCallback || function (id) { var _default = {}; exports.default = _default; -},{}]},{},[52]); +},{}]},{},[53]); From 57416c8ea31ef21faddb90fabffc43fee2604ac1 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 11:40:24 +0100 Subject: [PATCH 08/15] Build assets after merge Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 3 +++ dist/autofill.js | 3 +++ swift-package/Resources/assets/autofill-debug.js | 3 +++ swift-package/Resources/assets/autofill.js | 3 +++ 4 files changed, 12 insertions(+) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index b558ad4bd..763c77d3a 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -6487,6 +6487,9 @@ module.exports={ "astonmartinf1.com": { "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" }, + "auth.readymag.com": { + "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" + }, "autify.com": { "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" }, diff --git a/dist/autofill.js b/dist/autofill.js index f4228bed0..061c85d9d 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -2811,6 +2811,9 @@ module.exports={ "astonmartinf1.com": { "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" }, + "auth.readymag.com": { + "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" + }, "autify.com": { "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" }, diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index b558ad4bd..763c77d3a 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -6487,6 +6487,9 @@ module.exports={ "astonmartinf1.com": { "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" }, + "auth.readymag.com": { + "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" + }, "autify.com": { "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" }, diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index f4228bed0..061c85d9d 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -2811,6 +2811,9 @@ module.exports={ "astonmartinf1.com": { "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" }, + "auth.readymag.com": { + "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" + }, "autify.com": { "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" }, From 3fd620016bf151bd40476b82b805e102e9dddff1 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 12:32:13 +0100 Subject: [PATCH 09/15] Fix init timing Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 4 ++-- dist/autofill.js | 4 ++-- src/DeviceInterface/InterfacePrototype.js | 4 ++-- swift-package/Resources/assets/autofill-debug.js | 4 ++-- swift-package/Resources/assets/autofill.js | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 763c77d3a..e66a4e5b8 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -8575,6 +8575,7 @@ class InterfacePrototype { async startInit() { if (this.alreadyInitialized) return; + this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); await this.setupAutofill(); @@ -8591,8 +8592,6 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } - - this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -9667,6 +9666,7 @@ class Form { } }); this.categorizeInputs(); + console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); diff --git a/dist/autofill.js b/dist/autofill.js index 061c85d9d..d122eb734 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -4899,6 +4899,7 @@ class InterfacePrototype { async startInit() { if (this.alreadyInitialized) return; + this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); await this.setupAutofill(); @@ -4915,8 +4916,6 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } - - this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -5991,6 +5990,7 @@ class Form { } }); this.categorizeInputs(); + console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); diff --git a/src/DeviceInterface/InterfacePrototype.js b/src/DeviceInterface/InterfacePrototype.js index 30aed1a0a..a1ef73039 100644 --- a/src/DeviceInterface/InterfacePrototype.js +++ b/src/DeviceInterface/InterfacePrototype.js @@ -242,6 +242,8 @@ class InterfacePrototype { async startInit () { if (this.alreadyInitialized) return + this.alreadyInitialized = true + await this.refreshSettings() this.addDeviceListeners() @@ -262,8 +264,6 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { initFormSubmissionsApi(this.scanner.forms) } - - this.alreadyInitialized = true } /** diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 763c77d3a..e66a4e5b8 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -8575,6 +8575,7 @@ class InterfacePrototype { async startInit() { if (this.alreadyInitialized) return; + this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); await this.setupAutofill(); @@ -8591,8 +8592,6 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } - - this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -9667,6 +9666,7 @@ class Form { } }); this.categorizeInputs(); + console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 061c85d9d..d122eb734 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -4899,6 +4899,7 @@ class InterfacePrototype { async startInit() { if (this.alreadyInitialized) return; + this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); await this.setupAutofill(); @@ -4915,8 +4916,6 @@ class InterfacePrototype { if (this.settings.featureToggles.credentials_saving) { (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms); } - - this.alreadyInitialized = true; } /** * This is to aid the migration to all platforms using Settings.enabled. @@ -5991,6 +5990,7 @@ class Form { } }); this.categorizeInputs(); + console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); From 09fc6e9fbe4d92e74f54f076aa28ca4049ea4da3 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 12:35:35 +0100 Subject: [PATCH 10/15] Commit compiled assets Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 1 - dist/autofill.js | 1 - swift-package/Resources/assets/autofill-debug.js | 1 - swift-package/Resources/assets/autofill.js | 1 - 4 files changed, 4 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index e66a4e5b8..2d0638e8c 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -9666,7 +9666,6 @@ class Form { } }); this.categorizeInputs(); - console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); diff --git a/dist/autofill.js b/dist/autofill.js index d122eb734..0a5076883 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -5990,7 +5990,6 @@ class Form { } }); this.categorizeInputs(); - console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index e66a4e5b8..2d0638e8c 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -9666,7 +9666,6 @@ class Form { } }); this.categorizeInputs(); - console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index d122eb734..0a5076883 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -5990,7 +5990,6 @@ class Form { } }); this.categorizeInputs(); - console.log('rattone', this); if (shouldAutoprompt) { this.promptLoginIfNeeded(); From 65759ae7fd01fb33452a78f26472e55cc9c4ca0d Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Tue, 17 Jan 2023 17:53:22 +0100 Subject: [PATCH 11/15] More matching improvements + test cases Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 8 +- dist/autofill.js | 8 +- src/Form/selectors-css.js | 14 +- .../cookpad_signup-second-step.html | 20 +++ src/Form/test-cases/index.js | 4 +- src/Form/test-cases/samash_credit-card.html | 158 ++++++++++++++++++ .../Resources/assets/autofill-debug.js | 8 +- swift-package/Resources/assets/autofill.js | 8 +- 8 files changed, 207 insertions(+), 21 deletions(-) create mode 100644 src/Form/test-cases/cookpad_signup-second-step.html create mode 100644 src/Form/test-cases/samash_credit-card.html diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 2d0638e8c..b7598282c 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -13454,15 +13454,15 @@ const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not( exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; @@ -13481,7 +13481,7 @@ const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=bi const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/dist/autofill.js b/dist/autofill.js index 0a5076883..712284bf0 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -9778,15 +9778,15 @@ const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not( exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; @@ -9805,7 +9805,7 @@ const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=bi const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/src/Form/selectors-css.js b/src/Form/selectors-css.js index 951f3e1fc..2a34c8ea5 100644 --- a/src/Form/selectors-css.js +++ b/src/Form/selectors-css.js @@ -17,7 +17,6 @@ input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=f input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), -input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), @@ -66,21 +65,27 @@ input[name*=security i][name*=code i]` const expirationMonth = ` [autocomplete="cc-exp-month"], +[autocomplete="cc_exp_month"], [name="ccmonth"], [name="ppw-expirationDate_month"], [name=cardExpiryMonth], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], -[id*=expiration i][id*=month i]` +[id*=expiration i][id*=month i], +[name*=cc-exp-month i], +[name*=cc_exp_month i]` const expirationYear = ` [autocomplete="cc-exp-year"], +[autocomplete="cc_exp_year"], [name="ccyear"], [name="ppw-expirationDate_year"], [name=cardExpiryYear], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], -[id*=expiration i][id*=year i]` +[id*=expiration i][id*=year i], +[name*=cc-exp-year i], +[name*=cc_exp_year i]` const expiration = ` [autocomplete="cc-exp"], @@ -190,7 +195,8 @@ const username = [ `input[autocomplete=username]`, `input[name*=accountid i]`, `input[name="j_username" i]`, - `input[id="username" i]` + `input[id="username" i]`, + `input[placeholder^="username" i]` ] // todo: these are still used directly right now, mostly in scanForInputs diff --git a/src/Form/test-cases/cookpad_signup-second-step.html b/src/Form/test-cases/cookpad_signup-second-step.html new file mode 100644 index 000000000..ccaef1f21 --- /dev/null +++ b/src/Form/test-cases/cookpad_signup-second-step.html @@ -0,0 +1,20 @@ + +
+ + + +
+
+ + +
+
+
+
+ +
+
+ +
+ +
diff --git a/src/Form/test-cases/index.js b/src/Form/test-cases/index.js index f0e0a1eeb..e6ebcab0d 100644 --- a/src/Form/test-cases/index.js +++ b/src/Form/test-cases/index.js @@ -259,5 +259,7 @@ export default [ { html: 'postnews_login.html' }, { html: 'airbnb_signup.html' }, { html: 'cookpad_email-confirmation-code.html', expectedFailures: ['unknown'] }, - { html: 'instagram_email-confirmation-code.html' } + { html: 'instagram_email-confirmation-code.html' }, + { html: 'cookpad_signup-second-step.html' }, + { html: 'samash_credit-card.html' } ] diff --git a/src/Form/test-cases/samash_credit-card.html b/src/Form/test-cases/samash_credit-card.html new file mode 100644 index 000000000..3adba76d8 --- /dev/null +++ b/src/Form/test-cases/samash_credit-card.html @@ -0,0 +1,158 @@ + +
+ +
+
+
    + +
  • + + American Express + +
  • + +
  • + + Visa + +
  • + +
  • + + MasterCard + +
  • + +
  • + + Discover + +
  • + +
+ +
+ + Pay securely using your credit card. + +
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+
+
+ +
+
+
+ + + +
diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 2d0638e8c..b7598282c 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -13454,15 +13454,15 @@ const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not( exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; @@ -13481,7 +13481,7 @@ const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=bi const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 0a5076883..712284bf0 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -9778,15 +9778,15 @@ const FORM_INPUTS_SELECTOR = "\ninput:not([type=submit]):not([type=button]):not( exports.FORM_INPUTS_SELECTOR = FORM_INPUTS_SELECTOR; const SUBMIT_BUTTON_SELECTOR = "\ninput[type=submit],\ninput[type=button],\nbutton:not([role=switch]):not([role=link]),\n[role=button],\na[href=\"#\"][id*=button i],\na[href=\"#\"][id*=btn i]"; exports.SUBMIT_BUTTON_SELECTOR = SUBMIT_BUTTON_SELECTOR; -const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too +const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=\"\"][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]),\ninput:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]),\ninput[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=\"\"][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]),\ninput[type=email],\ninput[type=text][aria-label*=email i]:not([aria-label*=search i]),\ninput:not([type])[aria-label*=email i]:not([aria-label*=search i]),\ninput[name=username][type=email],\ninput[autocomplete=email]"; // We've seen non-standard types like 'user'. This selector should get them, too const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; @@ -9805,7 +9805,7 @@ const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=bi const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later From 740ca7bdb6e56441c89ceb18d7b0c5f1a9b93748 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Thu, 19 Jan 2023 11:29:23 +0100 Subject: [PATCH 12/15] Address PR feedback Signed-off-by: Emanuele Feliziani --- src/DeviceInterface/InterfacePrototype.js | 8 +- src/Form/selectors-css.js | 110 +++++++++++----------- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/DeviceInterface/InterfacePrototype.js b/src/DeviceInterface/InterfacePrototype.js index a1ef73039..66939f499 100644 --- a/src/DeviceInterface/InterfacePrototype.js +++ b/src/DeviceInterface/InterfacePrototype.js @@ -66,7 +66,7 @@ class InterfacePrototype { deviceApi; /** @type {boolean} */ - alreadyInitialized; + isInitializationStarted; /** * @param {GlobalConfig} config @@ -81,7 +81,7 @@ class InterfacePrototype { this.scanner = createScanner(this, { initialDelay: this.initialSetupDelayMs }) - this.alreadyInitialized = false + this.isInitializationStarted = false } /** @@ -240,7 +240,7 @@ class InterfacePrototype { } async startInit () { - if (this.alreadyInitialized) return + if (this.isInitializationStarted) return this.alreadyInitialized = true @@ -305,9 +305,9 @@ class InterfacePrototype { const handler = async () => { if (document.readyState === 'complete') { - await this.startInit() window.removeEventListener('load', handler) document.removeEventListener('readystatechange', handler) + await this.startInit() } } if (document.readyState === 'complete') { diff --git a/src/Form/selectors-css.js b/src/Form/selectors-css.js index 2a34c8ea5..ced67a8c9 100644 --- a/src/Form/selectors-css.js +++ b/src/Form/selectors-css.js @@ -30,22 +30,22 @@ input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]) const password = `input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])` const cardName = ` -input[autocomplete="cc-name"], -input[autocomplete="ccname"], -input[name="ccname"], -input[name="cc-name"], -input[name="ppw-accountHolderName"], +input[autocomplete="cc-name" i], +input[autocomplete="ccname" i], +input[name="ccname" i], +input[name="cc-name" i], +input[name="ppw-accountHolderName" i], input[id*=cardname i], input[id*=card-name i], input[id*=card_name i]` const cardNumber = ` -input[autocomplete="cc-number"], -input[autocomplete="ccnumber"], -input[autocomplete="cardnumber"], -input[autocomplete="card-number"], -input[name="ccnumber"], -input[name="cc-number"], +input[autocomplete="cc-number" i], +input[autocomplete="ccnumber" i], +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], input[name*=cardnumber i], input[id*=cardnumber i], @@ -53,22 +53,22 @@ input[id*=card-number i], input[id*=card_number i]` const cardSecurityCode = ` -input[autocomplete="cc-csc"], -input[autocomplete="csc"], -input[autocomplete="cc-cvc"], -input[autocomplete="cvc"], -input[name="cvc"], -input[name="cc-cvc"], -input[name="cc-csc"], -input[name="csc"], +input[autocomplete="cc-csc" i], +input[autocomplete="csc" i], +input[autocomplete="cc-cvc" i], +input[autocomplete="cvc" i], +input[name="cvc" i], +input[name="cc-cvc" i], +input[name="cc-csc" i], +input[name="csc" i], input[name*=security i][name*=code i]` const expirationMonth = ` -[autocomplete="cc-exp-month"], -[autocomplete="cc_exp_month"], -[name="ccmonth"], -[name="ppw-expirationDate_month"], -[name=cardExpiryMonth], +[autocomplete="cc-exp-month" i], +[autocomplete="cc_exp_month" i], +[name="ccmonth" i], +[name="ppw-expirationDate_month" i], +[name=cardExpiryMonth i], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], [id*=expiration i][id*=month i], @@ -76,11 +76,11 @@ const expirationMonth = ` [name*=cc_exp_month i]` const expirationYear = ` -[autocomplete="cc-exp-year"], -[autocomplete="cc_exp_year"], -[name="ccyear"], -[name="ppw-expirationDate_year"], -[name=cardExpiryYear], +[autocomplete="cc-exp-year" i], +[autocomplete="cc_exp_year" i], +[name="ccyear" i], +[name="ppw-expirationDate_year" i], +[name=cardExpiryYear i], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], [id*=expiration i][id*=year i], @@ -88,10 +88,10 @@ const expirationYear = ` [name*=cc_exp_year i]` const expiration = ` -[autocomplete="cc-exp"], -[name="cc-exp"], -[name="exp-date"], -[name="expirationDate"], +[autocomplete="cc-exp" i], +[name="cc-exp" i], +[name="exp-date" i], +[name="expirationDate" i], input[id*=expiration i]` const firstName = ` @@ -134,55 +134,55 @@ const phone = ` [name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [placeholder*="phone number" i]` const addressStreet1 = ` -[name=address], [autocomplete=street-address], [autocomplete=address-line1], -[name=street], -[name=ppw-line1], [name*=addressLine1 i]` +[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i], +[name=street i], +[name=ppw-line1 i], [name*=addressLine1 i]` const addressStreet2 = ` -[name=address], [autocomplete=address-line2], -[name=ppw-line2], [name*=addressLine2 i]` +[name=address2 i], [autocomplete=address-line2 i], +[name=ppw-line2 i], [name*=addressLine2 i]` const addressCity = ` -[name=city], [autocomplete=address-level2], -[name=ppw-city], [name*=addressCity i]` +[name=city i], [autocomplete=address-level2 i], +[name=ppw-city i], [name*=addressCity i]` const addressProvince = ` -[name=province], [name=state], [autocomplete=address-level1]` +[name=province i], [name=state i], [autocomplete=address-level1 i]` const addressPostalCode = ` -[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code], +[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i], [name*=postalCode i], [name*=zipcode i]` const addressCountryCode = [ - `[name=country], [autocomplete=country], + `[name=country i], [autocomplete=country i], [name*=countryCode i], [name*=country-code i], [name*=countryName i], [name*=country-name i]`, `select.idms-address-country` // Fix for Apple signup ] const birthdayDay = ` -[name=bday-day], +[name=bday-day i], [name*=birthday_day i], [name*=birthday-day i], -[name=date_of_birth_day], [name=date-of-birth-day], -[name^=birthdate_d], [name^=birthdate-d], +[name=date_of_birth_day i], [name=date-of-birth-day i], +[name^=birthdate_d i], [name^=birthdate-d i], [aria-label="birthday" i][placeholder="day" i]` const birthdayMonth = ` -[name=bday-month], +[name=bday-month i], [name*=birthday_month i], [name*=birthday-month i], -[name=date_of_birth_month], [name=date-of-birth-month], -[name^=birthdate_m], [name^=birthdate-m], -select[name="mm"]` +[name=date_of_birth_month i], [name=date-of-birth-month i], +[name^=birthdate_m i], [name^=birthdate-m i], +select[name="mm" i]` const birthdayYear = ` -[name=bday-year], +[name=bday-year i], [name*=birthday_year i], [name*=birthday-year i], -[name=date_of_birth_year], [name=date-of-birth-year], -[name^=birthdate_y], [name^=birthdate-y], +[name=date_of_birth_year i], [name=date-of-birth-year i], +[name^=birthdate_y i], [name^=birthdate-y i], [aria-label="birthday" i][placeholder="year" i]` const username = [ - `${GENERIC_TEXT_FIELD}[autocomplete^=user]`, + `${GENERIC_TEXT_FIELD}[autocomplete^=user i]`, `input[name=username i]`, // fix for `aa.com` `input[name="loginId" i]`, @@ -192,7 +192,7 @@ const username = [ `input[name="user-id" i]`, `input[id="login-id" i]`, `input[name=accountname i]`, - `input[autocomplete=username]`, + `input[autocomplete=username i]`, `input[name*=accountid i]`, `input[name="j_username" i]`, `input[id="username" i]`, From 5c64f07ba098bd784291b834988b736c41ec7e49 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Thu, 19 Jan 2023 11:29:38 +0100 Subject: [PATCH 13/15] Fix substack failing + test Signed-off-by: Emanuele Feliziani --- src/Form/matching-configuration.js | 2 +- src/Form/test-cases/index.js | 3 ++- src/Form/test-cases/substack_login.html | 13 +++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/Form/test-cases/substack_login.html diff --git a/src/Form/matching-configuration.js b/src/Form/matching-configuration.js index 7fe39b640..e2b33b287 100644 --- a/src/Form/matching-configuration.js +++ b/src/Form/matching-configuration.js @@ -256,7 +256,7 @@ const matchingConfiguration = { ddgMatcher: { matchers: { email: {match: '.mail\\b|apple.?id', skip: 'phone|name|reservation number|code', forceUnknown: 'search|filter|subject|title|\btab\b'}, - password: {match: 'password', forceUnknown: 'captcha|mfa|2fa|two factor'}, + password: {match: 'password', skip: 'email', forceUnknown: 'captcha|mfa|2fa|two factor'}, username: {match: '(user|account|login|net)((.)?(name|id|login).?)?(.?(or|/).+)?$|benutzername', forceUnknown: 'search|policy'}, // CC diff --git a/src/Form/test-cases/index.js b/src/Form/test-cases/index.js index e6ebcab0d..4064abe10 100644 --- a/src/Form/test-cases/index.js +++ b/src/Form/test-cases/index.js @@ -261,5 +261,6 @@ export default [ { html: 'cookpad_email-confirmation-code.html', expectedFailures: ['unknown'] }, { html: 'instagram_email-confirmation-code.html' }, { html: 'cookpad_signup-second-step.html' }, - { html: 'samash_credit-card.html' } + { html: 'samash_credit-card.html' }, + { html: 'substack_login.html' } ] diff --git a/src/Form/test-cases/substack_login.html b/src/Form/test-cases/substack_login.html new file mode 100644 index 000000000..7f7cc3c9a --- /dev/null +++ b/src/Form/test-cases/substack_login.html @@ -0,0 +1,13 @@ + +
+
+
+
+ + +
+
From aa286c116b7cfbad3e7f32c3d5825235ea6d892e Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Thu, 19 Jan 2023 11:53:08 +0100 Subject: [PATCH 14/15] Remove useless test helper Signed-off-by: Emanuele Feliziani --- integration-test/helpers/pages.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/integration-test/helpers/pages.js b/integration-test/helpers/pages.js index 5ffdf9376..c2ca9a08e 100644 --- a/integration-test/helpers/pages.js +++ b/integration-test/helpers/pages.js @@ -188,11 +188,6 @@ export function loginPage (page, server, opts = {}) { const passwordStyle = await page.locator('#password').getAttribute('style') expect(passwordStyle || '').not.toContain('data:image/svg+xml;base64,') }, - async emailHasDaxPasswordHasKey () { - await this.emailFieldShowsDax() - const passwordStyle = await page.locator('#password').getAttribute('style') - expect(passwordStyle || '').toContain(constants.iconMatchers.key) - }, async onlyPasswordFieldHasIcon () { const styles1 = await page.locator('#email').getAttribute('style') const styles2 = await page.locator('#password').getAttribute('style') From 10e1780f12915a5bdb3f740fe41d5eb9024270f4 Mon Sep 17 00:00:00 2001 From: Emanuele Feliziani Date: Thu, 19 Jan 2023 11:54:20 +0100 Subject: [PATCH 15/15] Improve test comments Signed-off-by: Emanuele Feliziani --- dist/autofill-debug.js | 43 ++++++++++--------- dist/autofill.js | 43 ++++++++++--------- src/Form/matching.test.js | 12 +++--- .../Resources/assets/autofill-debug.js | 43 ++++++++++--------- swift-package/Resources/assets/autofill.js | 43 ++++++++++--------- 5 files changed, 94 insertions(+), 90 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index b7598282c..0b4a0cd6d 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -8380,7 +8380,7 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); - _defineProperty(this, "alreadyInitialized", void 0); + _defineProperty(this, "isInitializationStarted", void 0); _classPrivateFieldInitSpec(this, _data2, { writable: true, @@ -8399,7 +8399,7 @@ class InterfacePrototype { this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); - this.alreadyInitialized = false; + this.isInitializationStarted = false; } /** * Implementors should override this with a UI controller that suits @@ -8574,7 +8574,7 @@ class InterfacePrototype { } async startInit() { - if (this.alreadyInitialized) return; + if (this.isInitializationStarted) return; this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); @@ -8635,9 +8635,9 @@ class InterfacePrototype { const handler = async () => { if (document.readyState === 'complete') { - await this.startInit(); window.removeEventListener('load', handler); document.removeEventListener('readystatechange', handler); + await this.startInit(); } }; @@ -12038,6 +12038,7 @@ const matchingConfiguration = { }, password: { match: 'password', + skip: 'email', forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { @@ -13458,30 +13459,30 @@ const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):n const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; -const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; -const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; -const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; -const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; +const cardName = "\ninput[autocomplete=\"cc-name\" i],\ninput[autocomplete=\"ccname\" i],\ninput[name=\"ccname\" i],\ninput[name=\"cc-name\" i],\ninput[name=\"ppw-accountHolderName\" i],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; +const cardNumber = "\ninput[autocomplete=\"cc-number\" i],\ninput[autocomplete=\"ccnumber\" i],\ninput[autocomplete=\"cardnumber\" i],\ninput[autocomplete=\"card-number\" i],\ninput[name=\"ccnumber\" i],\ninput[name=\"cc-number\" i],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; +const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\" i],\ninput[autocomplete=\"csc\" i],\ninput[autocomplete=\"cc-cvc\" i],\ninput[autocomplete=\"cvc\" i],\ninput[name=\"cvc\" i],\ninput[name=\"cc-cvc\" i],\ninput[name=\"cc-csc\" i],\ninput[name=\"csc\" i],\ninput[name*=security i][name*=code i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\" i],\n[autocomplete=\"cc_exp_month\" i],\n[name=\"ccmonth\" i],\n[name=\"ppw-expirationDate_month\" i],\n[name=cardExpiryMonth i],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\" i],\n[autocomplete=\"cc_exp_year\" i],\n[name=\"ccyear\" i],\n[name=\"ppw-expirationDate_year\" i],\n[name=cardExpiryYear i],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; +const expiration = "\n[autocomplete=\"cc-exp\" i],\n[name=\"cc-exp\" i],\n[name=\"exp-date\" i],\n[name=\"expirationDate\" i],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; const lastName = "\n[name=lname], [autocomplete*=family-name i],\n[name*=lastname i], [autocomplete*=lastname i],\n[name*=last-name i], [autocomplete*=last-name i],\n[name*=last_name i], [autocomplete*=last_name i],\n[name*=familyname i], [autocomplete*=familyname i],\n[name*=family-name i],\n[name*=family_name i], [autocomplete*=family_name i],\n[name*=surname i], [autocomplete*=surname i]"; const fullName = "\n[name=name], [autocomplete=name],\n[name*=fullname i], [autocomplete*=fullname i],\n[name*=full-name i], [autocomplete*=full-name i],\n[name*=full_name i], [autocomplete*=full_name i],\n[name*=your-name i], [autocomplete*=your-name i]"; const phone = "\n[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [placeholder*=\"phone number\" i]"; -const addressStreet1 = "\n[name=address], [autocomplete=street-address], [autocomplete=address-line1],\n[name=street],\n[name=ppw-line1], [name*=addressLine1 i]"; -const addressStreet2 = "\n[name=address], [autocomplete=address-line2],\n[name=ppw-line2], [name*=addressLine2 i]"; -const addressCity = "\n[name=city], [autocomplete=address-level2],\n[name=ppw-city], [name*=addressCity i]"; -const addressProvince = "\n[name=province], [name=state], [autocomplete=address-level1]"; -const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; -const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup +const addressStreet1 = "\n[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i],\n[name=street i],\n[name=ppw-line1 i], [name*=addressLine1 i]"; +const addressStreet2 = "\n[name=address2 i], [autocomplete=address-line2 i],\n[name=ppw-line2 i], [name*=addressLine2 i]"; +const addressCity = "\n[name=city i], [autocomplete=address-level2 i],\n[name=ppw-city i], [name*=addressCity i]"; +const addressProvince = "\n[name=province i], [name=state i], [autocomplete=address-level1 i]"; +const addressPostalCode = "\n[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i],\n[name*=postalCode i], [name*=zipcode i]"; +const addressCountryCode = ["[name=country i], [autocomplete=country i],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; -const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` +const birthdayDay = "\n[name=bday-day i],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day i], [name=date-of-birth-day i],\n[name^=birthdate_d i], [name^=birthdate-d i],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month i],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month i], [name=date-of-birth-month i],\n[name^=birthdate_m i], [name^=birthdate-m i],\nselect[name=\"mm\" i]"; +const birthdayYear = "\n[name=bday-year i],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year i], [name=date-of-birth-year i],\n[name^=birthdate_y i], [name^=birthdate-y i],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user i]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username i]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/dist/autofill.js b/dist/autofill.js index 712284bf0..f95b817d0 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -4704,7 +4704,7 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); - _defineProperty(this, "alreadyInitialized", void 0); + _defineProperty(this, "isInitializationStarted", void 0); _classPrivateFieldInitSpec(this, _data2, { writable: true, @@ -4723,7 +4723,7 @@ class InterfacePrototype { this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); - this.alreadyInitialized = false; + this.isInitializationStarted = false; } /** * Implementors should override this with a UI controller that suits @@ -4898,7 +4898,7 @@ class InterfacePrototype { } async startInit() { - if (this.alreadyInitialized) return; + if (this.isInitializationStarted) return; this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); @@ -4959,9 +4959,9 @@ class InterfacePrototype { const handler = async () => { if (document.readyState === 'complete') { - await this.startInit(); window.removeEventListener('load', handler); document.removeEventListener('readystatechange', handler); + await this.startInit(); } }; @@ -8362,6 +8362,7 @@ const matchingConfiguration = { }, password: { match: 'password', + skip: 'email', forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { @@ -9782,30 +9783,30 @@ const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):n const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; -const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; -const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; -const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; -const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; +const cardName = "\ninput[autocomplete=\"cc-name\" i],\ninput[autocomplete=\"ccname\" i],\ninput[name=\"ccname\" i],\ninput[name=\"cc-name\" i],\ninput[name=\"ppw-accountHolderName\" i],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; +const cardNumber = "\ninput[autocomplete=\"cc-number\" i],\ninput[autocomplete=\"ccnumber\" i],\ninput[autocomplete=\"cardnumber\" i],\ninput[autocomplete=\"card-number\" i],\ninput[name=\"ccnumber\" i],\ninput[name=\"cc-number\" i],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; +const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\" i],\ninput[autocomplete=\"csc\" i],\ninput[autocomplete=\"cc-cvc\" i],\ninput[autocomplete=\"cvc\" i],\ninput[name=\"cvc\" i],\ninput[name=\"cc-cvc\" i],\ninput[name=\"cc-csc\" i],\ninput[name=\"csc\" i],\ninput[name*=security i][name*=code i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\" i],\n[autocomplete=\"cc_exp_month\" i],\n[name=\"ccmonth\" i],\n[name=\"ppw-expirationDate_month\" i],\n[name=cardExpiryMonth i],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\" i],\n[autocomplete=\"cc_exp_year\" i],\n[name=\"ccyear\" i],\n[name=\"ppw-expirationDate_year\" i],\n[name=cardExpiryYear i],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; +const expiration = "\n[autocomplete=\"cc-exp\" i],\n[name=\"cc-exp\" i],\n[name=\"exp-date\" i],\n[name=\"expirationDate\" i],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; const lastName = "\n[name=lname], [autocomplete*=family-name i],\n[name*=lastname i], [autocomplete*=lastname i],\n[name*=last-name i], [autocomplete*=last-name i],\n[name*=last_name i], [autocomplete*=last_name i],\n[name*=familyname i], [autocomplete*=familyname i],\n[name*=family-name i],\n[name*=family_name i], [autocomplete*=family_name i],\n[name*=surname i], [autocomplete*=surname i]"; const fullName = "\n[name=name], [autocomplete=name],\n[name*=fullname i], [autocomplete*=fullname i],\n[name*=full-name i], [autocomplete*=full-name i],\n[name*=full_name i], [autocomplete*=full_name i],\n[name*=your-name i], [autocomplete*=your-name i]"; const phone = "\n[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [placeholder*=\"phone number\" i]"; -const addressStreet1 = "\n[name=address], [autocomplete=street-address], [autocomplete=address-line1],\n[name=street],\n[name=ppw-line1], [name*=addressLine1 i]"; -const addressStreet2 = "\n[name=address], [autocomplete=address-line2],\n[name=ppw-line2], [name*=addressLine2 i]"; -const addressCity = "\n[name=city], [autocomplete=address-level2],\n[name=ppw-city], [name*=addressCity i]"; -const addressProvince = "\n[name=province], [name=state], [autocomplete=address-level1]"; -const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; -const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup +const addressStreet1 = "\n[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i],\n[name=street i],\n[name=ppw-line1 i], [name*=addressLine1 i]"; +const addressStreet2 = "\n[name=address2 i], [autocomplete=address-line2 i],\n[name=ppw-line2 i], [name*=addressLine2 i]"; +const addressCity = "\n[name=city i], [autocomplete=address-level2 i],\n[name=ppw-city i], [name*=addressCity i]"; +const addressProvince = "\n[name=province i], [name=state i], [autocomplete=address-level1 i]"; +const addressPostalCode = "\n[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i],\n[name*=postalCode i], [name*=zipcode i]"; +const addressCountryCode = ["[name=country i], [autocomplete=country i],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; -const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` +const birthdayDay = "\n[name=bday-day i],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day i], [name=date-of-birth-day i],\n[name^=birthdate_d i], [name^=birthdate-d i],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month i],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month i], [name=date-of-birth-month i],\n[name^=birthdate_m i], [name^=birthdate-m i],\nselect[name=\"mm\" i]"; +const birthdayYear = "\n[name=bday-year i],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year i], [name=date-of-birth-year i],\n[name^=birthdate_y i], [name^=birthdate-y i],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user i]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username i]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/src/Form/matching.test.js b/src/Form/matching.test.js index 2bd8657fe..7a998d02f 100644 --- a/src/Form/matching.test.js +++ b/src/Form/matching.test.js @@ -116,34 +116,34 @@ describe('matching', () => { subtype: 'identities.firstName' }, { - // respects the options parameter + // when hybrid with no credentials, use credentials by default (nothing shows) html: ``, subtype: 'credentials.username', opts: {isHybrid: true, hasCredentials: false} }, { - // respects the options parameter + // when hybrid with no credentials but we support identities, show identities html: ``, subtype: 'identities.emailAddress', opts: {isHybrid: true, hasCredentials: false, supportsIdentitiesAutofill: true} }, { - // respects the options parameter + // when login with no credentials but we support identities, show identities html: ``, subtype: 'identities.emailAddress', opts: {isLogin: true, hasCredentials: false, supportsIdentitiesAutofill: true} }, { - // respects the options parameter + // when login with credentials, show credentials regardless of identities html: ``, subtype: 'credentials.username', opts: {isLogin: true, hasCredentials: true, supportsIdentitiesAutofill: true} }, { - // respects the options parameter + // when hybrid with credentials, show credentials regardless of identities html: ``, subtype: 'credentials.username', - opts: {isHybrid: true, hasCredentials: true} + opts: {isHybrid: true, hasCredentials: true, supportsIdentitiesAutofill: true} } ])(`$html should be '$subtype'`, (args) => { const { html, subtype, opts } = args diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index b7598282c..0b4a0cd6d 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -8380,7 +8380,7 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); - _defineProperty(this, "alreadyInitialized", void 0); + _defineProperty(this, "isInitializationStarted", void 0); _classPrivateFieldInitSpec(this, _data2, { writable: true, @@ -8399,7 +8399,7 @@ class InterfacePrototype { this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); - this.alreadyInitialized = false; + this.isInitializationStarted = false; } /** * Implementors should override this with a UI controller that suits @@ -8574,7 +8574,7 @@ class InterfacePrototype { } async startInit() { - if (this.alreadyInitialized) return; + if (this.isInitializationStarted) return; this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); @@ -8635,9 +8635,9 @@ class InterfacePrototype { const handler = async () => { if (document.readyState === 'complete') { - await this.startInit(); window.removeEventListener('load', handler); document.removeEventListener('readystatechange', handler); + await this.startInit(); } }; @@ -12038,6 +12038,7 @@ const matchingConfiguration = { }, password: { match: 'password', + skip: 'email', forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { @@ -13458,30 +13459,30 @@ const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):n const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; -const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; -const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; -const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; -const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; +const cardName = "\ninput[autocomplete=\"cc-name\" i],\ninput[autocomplete=\"ccname\" i],\ninput[name=\"ccname\" i],\ninput[name=\"cc-name\" i],\ninput[name=\"ppw-accountHolderName\" i],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; +const cardNumber = "\ninput[autocomplete=\"cc-number\" i],\ninput[autocomplete=\"ccnumber\" i],\ninput[autocomplete=\"cardnumber\" i],\ninput[autocomplete=\"card-number\" i],\ninput[name=\"ccnumber\" i],\ninput[name=\"cc-number\" i],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; +const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\" i],\ninput[autocomplete=\"csc\" i],\ninput[autocomplete=\"cc-cvc\" i],\ninput[autocomplete=\"cvc\" i],\ninput[name=\"cvc\" i],\ninput[name=\"cc-cvc\" i],\ninput[name=\"cc-csc\" i],\ninput[name=\"csc\" i],\ninput[name*=security i][name*=code i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\" i],\n[autocomplete=\"cc_exp_month\" i],\n[name=\"ccmonth\" i],\n[name=\"ppw-expirationDate_month\" i],\n[name=cardExpiryMonth i],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\" i],\n[autocomplete=\"cc_exp_year\" i],\n[name=\"ccyear\" i],\n[name=\"ppw-expirationDate_year\" i],\n[name=cardExpiryYear i],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; +const expiration = "\n[autocomplete=\"cc-exp\" i],\n[name=\"cc-exp\" i],\n[name=\"exp-date\" i],\n[name=\"expirationDate\" i],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; const lastName = "\n[name=lname], [autocomplete*=family-name i],\n[name*=lastname i], [autocomplete*=lastname i],\n[name*=last-name i], [autocomplete*=last-name i],\n[name*=last_name i], [autocomplete*=last_name i],\n[name*=familyname i], [autocomplete*=familyname i],\n[name*=family-name i],\n[name*=family_name i], [autocomplete*=family_name i],\n[name*=surname i], [autocomplete*=surname i]"; const fullName = "\n[name=name], [autocomplete=name],\n[name*=fullname i], [autocomplete*=fullname i],\n[name*=full-name i], [autocomplete*=full-name i],\n[name*=full_name i], [autocomplete*=full_name i],\n[name*=your-name i], [autocomplete*=your-name i]"; const phone = "\n[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [placeholder*=\"phone number\" i]"; -const addressStreet1 = "\n[name=address], [autocomplete=street-address], [autocomplete=address-line1],\n[name=street],\n[name=ppw-line1], [name*=addressLine1 i]"; -const addressStreet2 = "\n[name=address], [autocomplete=address-line2],\n[name=ppw-line2], [name*=addressLine2 i]"; -const addressCity = "\n[name=city], [autocomplete=address-level2],\n[name=ppw-city], [name*=addressCity i]"; -const addressProvince = "\n[name=province], [name=state], [autocomplete=address-level1]"; -const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; -const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup +const addressStreet1 = "\n[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i],\n[name=street i],\n[name=ppw-line1 i], [name*=addressLine1 i]"; +const addressStreet2 = "\n[name=address2 i], [autocomplete=address-line2 i],\n[name=ppw-line2 i], [name*=addressLine2 i]"; +const addressCity = "\n[name=city i], [autocomplete=address-level2 i],\n[name=ppw-city i], [name*=addressCity i]"; +const addressProvince = "\n[name=province i], [name=state i], [autocomplete=address-level1 i]"; +const addressPostalCode = "\n[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i],\n[name*=postalCode i], [name*=zipcode i]"; +const addressCountryCode = ["[name=country i], [autocomplete=country i],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; -const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` +const birthdayDay = "\n[name=bday-day i],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day i], [name=date-of-birth-day i],\n[name^=birthdate_d i], [name^=birthdate-d i],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month i],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month i], [name=date-of-birth-month i],\n[name^=birthdate_m i], [name^=birthdate-m i],\nselect[name=\"mm\" i]"; +const birthdayYear = "\n[name=bday-year i],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year i], [name=date-of-birth-year i],\n[name^=birthdate_y i], [name^=birthdate-y i],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user i]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username i]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 712284bf0..f95b817d0 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -4704,7 +4704,7 @@ class InterfacePrototype { _defineProperty(this, "deviceApi", void 0); - _defineProperty(this, "alreadyInitialized", void 0); + _defineProperty(this, "isInitializationStarted", void 0); _classPrivateFieldInitSpec(this, _data2, { writable: true, @@ -4723,7 +4723,7 @@ class InterfacePrototype { this.scanner = (0, _Scanner.createScanner)(this, { initialDelay: this.initialSetupDelayMs }); - this.alreadyInitialized = false; + this.isInitializationStarted = false; } /** * Implementors should override this with a UI controller that suits @@ -4898,7 +4898,7 @@ class InterfacePrototype { } async startInit() { - if (this.alreadyInitialized) return; + if (this.isInitializationStarted) return; this.alreadyInitialized = true; await this.refreshSettings(); this.addDeviceListeners(); @@ -4959,9 +4959,9 @@ class InterfacePrototype { const handler = async () => { if (document.readyState === 'complete') { - await this.startInit(); window.removeEventListener('load', handler); document.removeEventListener('readystatechange', handler); + await this.startInit(); } }; @@ -8362,6 +8362,7 @@ const matchingConfiguration = { }, password: { match: 'password', + skip: 'email', forceUnknown: 'captcha|mfa|2fa|two factor' }, username: { @@ -9782,30 +9783,30 @@ const email = "\ninput:not([type])[name*=email i]:not([placeholder*=search i]):n const GENERIC_TEXT_FIELD = "\ninput:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=file]):not([type=hidden]):not([type=month]):not([type=number]):not([type=radio]):not([type=range]):not([type=reset]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week])"; const password = "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i])"; -const cardName = "\ninput[autocomplete=\"cc-name\"],\ninput[autocomplete=\"ccname\"],\ninput[name=\"ccname\"],\ninput[name=\"cc-name\"],\ninput[name=\"ppw-accountHolderName\"],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; -const cardNumber = "\ninput[autocomplete=\"cc-number\"],\ninput[autocomplete=\"ccnumber\"],\ninput[autocomplete=\"cardnumber\"],\ninput[autocomplete=\"card-number\"],\ninput[name=\"ccnumber\"],\ninput[name=\"cc-number\"],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; -const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\"],\ninput[autocomplete=\"csc\"],\ninput[autocomplete=\"cc-cvc\"],\ninput[autocomplete=\"cvc\"],\ninput[name=\"cvc\"],\ninput[name=\"cc-cvc\"],\ninput[name=\"cc-csc\"],\ninput[name=\"csc\"],\ninput[name*=security i][name*=code i]"; -const expirationMonth = "\n[autocomplete=\"cc-exp-month\"],\n[autocomplete=\"cc_exp_month\"],\n[name=\"ccmonth\"],\n[name=\"ppw-expirationDate_month\"],\n[name=cardExpiryMonth],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; -const expirationYear = "\n[autocomplete=\"cc-exp-year\"],\n[autocomplete=\"cc_exp_year\"],\n[name=\"ccyear\"],\n[name=\"ppw-expirationDate_year\"],\n[name=cardExpiryYear],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; -const expiration = "\n[autocomplete=\"cc-exp\"],\n[name=\"cc-exp\"],\n[name=\"exp-date\"],\n[name=\"expirationDate\"],\ninput[id*=expiration i]"; +const cardName = "\ninput[autocomplete=\"cc-name\" i],\ninput[autocomplete=\"ccname\" i],\ninput[name=\"ccname\" i],\ninput[name=\"cc-name\" i],\ninput[name=\"ppw-accountHolderName\" i],\ninput[id*=cardname i],\ninput[id*=card-name i],\ninput[id*=card_name i]"; +const cardNumber = "\ninput[autocomplete=\"cc-number\" i],\ninput[autocomplete=\"ccnumber\" i],\ninput[autocomplete=\"cardnumber\" i],\ninput[autocomplete=\"card-number\" i],\ninput[name=\"ccnumber\" i],\ninput[name=\"cc-number\" i],\ninput[name*=card i][name*=number i],\ninput[name*=cardnumber i],\ninput[id*=cardnumber i],\ninput[id*=card-number i],\ninput[id*=card_number i]"; +const cardSecurityCode = "\ninput[autocomplete=\"cc-csc\" i],\ninput[autocomplete=\"csc\" i],\ninput[autocomplete=\"cc-cvc\" i],\ninput[autocomplete=\"cvc\" i],\ninput[name=\"cvc\" i],\ninput[name=\"cc-cvc\" i],\ninput[name=\"cc-csc\" i],\ninput[name=\"csc\" i],\ninput[name*=security i][name*=code i]"; +const expirationMonth = "\n[autocomplete=\"cc-exp-month\" i],\n[autocomplete=\"cc_exp_month\" i],\n[name=\"ccmonth\" i],\n[name=\"ppw-expirationDate_month\" i],\n[name=cardExpiryMonth i],\n[name*=ExpDate_Month i],\n[name*=expiration i][name*=month i],\n[id*=expiration i][id*=month i],\n[name*=cc-exp-month i],\n[name*=cc_exp_month i]"; +const expirationYear = "\n[autocomplete=\"cc-exp-year\" i],\n[autocomplete=\"cc_exp_year\" i],\n[name=\"ccyear\" i],\n[name=\"ppw-expirationDate_year\" i],\n[name=cardExpiryYear i],\n[name*=ExpDate_Year i],\n[name*=expiration i][name*=year i],\n[id*=expiration i][id*=year i],\n[name*=cc-exp-year i],\n[name*=cc_exp_year i]"; +const expiration = "\n[autocomplete=\"cc-exp\" i],\n[name=\"cc-exp\" i],\n[name=\"exp-date\" i],\n[name=\"expirationDate\" i],\ninput[id*=expiration i]"; const firstName = "\n[name*=fname i], [autocomplete*=given-name i],\n[name*=firstname i], [autocomplete*=firstname i],\n[name*=first-name i], [autocomplete*=first-name i],\n[name*=first_name i], [autocomplete*=first_name i],\n[name*=givenname i], [autocomplete*=givenname i],\n[name*=given-name i],\n[name*=given_name i], [autocomplete*=given_name i],\n[name*=forename i], [autocomplete*=forename i]"; const middleName = "\n[name*=mname i], [autocomplete*=additional-name i],\n[name*=middlename i], [autocomplete*=middlename i],\n[name*=middle-name i], [autocomplete*=middle-name i],\n[name*=middle_name i], [autocomplete*=middle_name i],\n[name*=additionalname i], [autocomplete*=additionalname i],\n[name*=additional-name i],\n[name*=additional_name i], [autocomplete*=additional_name i]"; const lastName = "\n[name=lname], [autocomplete*=family-name i],\n[name*=lastname i], [autocomplete*=lastname i],\n[name*=last-name i], [autocomplete*=last-name i],\n[name*=last_name i], [autocomplete*=last_name i],\n[name*=familyname i], [autocomplete*=familyname i],\n[name*=family-name i],\n[name*=family_name i], [autocomplete*=family_name i],\n[name*=surname i], [autocomplete*=surname i]"; const fullName = "\n[name=name], [autocomplete=name],\n[name*=fullname i], [autocomplete*=fullname i],\n[name*=full-name i], [autocomplete*=full-name i],\n[name*=full_name i], [autocomplete*=full_name i],\n[name*=your-name i], [autocomplete*=your-name i]"; const phone = "\n[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [placeholder*=\"phone number\" i]"; -const addressStreet1 = "\n[name=address], [autocomplete=street-address], [autocomplete=address-line1],\n[name=street],\n[name=ppw-line1], [name*=addressLine1 i]"; -const addressStreet2 = "\n[name=address], [autocomplete=address-line2],\n[name=ppw-line2], [name*=addressLine2 i]"; -const addressCity = "\n[name=city], [autocomplete=address-level2],\n[name=ppw-city], [name*=addressCity i]"; -const addressProvince = "\n[name=province], [name=state], [autocomplete=address-level1]"; -const addressPostalCode = "\n[name=zip], [name=zip2], [name=postal], [autocomplete=postal-code], [autocomplete=zip-code],\n[name*=postalCode i], [name*=zipcode i]"; -const addressCountryCode = ["[name=country], [autocomplete=country],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup +const addressStreet1 = "\n[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i],\n[name=street i],\n[name=ppw-line1 i], [name*=addressLine1 i]"; +const addressStreet2 = "\n[name=address2 i], [autocomplete=address-line2 i],\n[name=ppw-line2 i], [name*=addressLine2 i]"; +const addressCity = "\n[name=city i], [autocomplete=address-level2 i],\n[name=ppw-city i], [name*=addressCity i]"; +const addressProvince = "\n[name=province i], [name=state i], [autocomplete=address-level1 i]"; +const addressPostalCode = "\n[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i],\n[name*=postalCode i], [name*=zipcode i]"; +const addressCountryCode = ["[name=country i], [autocomplete=country i],\n [name*=countryCode i], [name*=country-code i],\n [name*=countryName i], [name*=country-name i]", "select.idms-address-country" // Fix for Apple signup ]; -const birthdayDay = "\n[name=bday-day],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day], [name=date-of-birth-day],\n[name^=birthdate_d], [name^=birthdate-d],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; -const birthdayMonth = "\n[name=bday-month],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month], [name=date-of-birth-month],\n[name^=birthdate_m], [name^=birthdate-m],\nselect[name=\"mm\"]"; -const birthdayYear = "\n[name=bday-year],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year], [name=date-of-birth-year],\n[name^=birthdate_y], [name^=birthdate-y],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; -const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user]"), "input[name=username i]", // fix for `aa.com` +const birthdayDay = "\n[name=bday-day i],\n[name*=birthday_day i], [name*=birthday-day i],\n[name=date_of_birth_day i], [name=date-of-birth-day i],\n[name^=birthdate_d i], [name^=birthdate-d i],\n[aria-label=\"birthday\" i][placeholder=\"day\" i]"; +const birthdayMonth = "\n[name=bday-month i],\n[name*=birthday_month i], [name*=birthday-month i],\n[name=date_of_birth_month i], [name=date-of-birth-month i],\n[name^=birthdate_m i], [name^=birthdate-m i],\nselect[name=\"mm\" i]"; +const birthdayYear = "\n[name=bday-year i],\n[name*=birthday_year i], [name*=birthday-year i],\n[name=date_of_birth_year i], [name=date-of-birth-year i],\n[name^=birthdate_y i], [name^=birthdate-y i],\n[aria-label=\"birthday\" i][placeholder=\"year\" i]"; +const username = ["".concat(GENERIC_TEXT_FIELD, "[autocomplete^=user i]"), "input[name=username i]", // fix for `aa.com` "input[name=\"loginId\" i]", // fix for https://online.mbank.pl/pl/Login -"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs +"input[name=\"userid\" i]", "input[name=\"user_id\" i]", "input[name=\"user-id\" i]", "input[id=\"login-id\" i]", "input[name=accountname i]", "input[autocomplete=username i]", "input[name*=accountid i]", "input[name=\"j_username\" i]", "input[id=\"username\" i]", "input[placeholder^=\"username\" i]"]; // todo: these are still used directly right now, mostly in scanForInputs // todo: ensure these can be set via configuration // Exported here for now, to be moved to configuration later