From 0dd5b720308596b8510031be23a5744c4a90e941 Mon Sep 17 00:00:00 2001 From: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:06:34 +1100 Subject: [PATCH] Update autofill to 8.4.2 (#3642) Task/Issue URL: https://app.asana.com/0/1205691418983700/1205691418983700 Autofill Release: https://github.com/duckduckgo/duckduckgo-autofill/releases/tag/8.4.2 ## Description Updates Autofill to version [8.4.2](https://github.com/duckduckgo/duckduckgo-autofill/releases/tag/8.4.2). ### Autofill 8.4.2 release notes ## What's Changed * Ema/perf followups by @GioSensation in https://github.com/duckduckgo/duckduckgo-autofill/pull/386 * Fix handler regression by @GioSensation in https://github.com/duckduckgo/duckduckgo-autofill/pull/392 * Bump markdown-it from 13.0.1 to 13.0.2 by @dependabot in https://github.com/duckduckgo/duckduckgo-autofill/pull/388 * Revert mutation observer for forms by @GioSensation in https://github.com/duckduckgo/duckduckgo-autofill/pull/396 **Full Changelog**: https://github.com/duckduckgo/duckduckgo-autofill/compare/8.4.1...8.4.2 ## Steps to test This release has been tested during autofill development. For smoke test steps see [this task](https://app.asana.com/0/1198964220583541/1200583647142330/f). Co-authored-by: GioSensation --- .../autofill/dist/autofill-debug.js | 138 +++++++++--------- .../@duckduckgo/autofill/dist/autofill.js | 138 +++++++++--------- package-lock.json | 4 +- package.json | 2 +- 4 files changed, 133 insertions(+), 149 deletions(-) diff --git a/node_modules/@duckduckgo/autofill/dist/autofill-debug.js b/node_modules/@duckduckgo/autofill/dist/autofill-debug.js index 3472e1d2a5ae..5ff88f0a31f8 100644 --- a/node_modules/@duckduckgo/autofill/dist/autofill-debug.js +++ b/node_modules/@duckduckgo/autofill/dist/autofill-debug.js @@ -9262,7 +9262,7 @@ function initFormSubmissionsApi(forms, matching) { const button = /** @type HTMLElement */event.target?.closest(selector); if (!button) return; const text = (0, _autofillUtils.getTextShallow)(button) || (0, _labelUtil.extractElementStrings)(button).join(' '); - const hasRelevantText = matching.getDDGMatcherRegex('submitButtonRegex')?.test(text); + const hasRelevantText = (0, _autofillUtils.safeRegexTest)(matching.getDDGMatcherRegex('submitButtonRegex'), text); if (hasRelevantText && text.length < 25) { // check if there's a form with values const filledForm = [...forms.values()].find(form => form.hasValues()); @@ -9287,7 +9287,7 @@ function initFormSubmissionsApi(forms, matching) { const observer = new PerformanceObserver(list => { const entries = list.getEntries().filter(entry => // @ts-ignore why does TS not know about `entry.initiatorType`? - ['fetch', 'xmlhttprequest'].includes(entry.initiatorType) && /login|sign-in|signin/.test(entry.name)); + ['fetch', 'xmlhttprequest'].includes(entry.initiatorType) && (0, _autofillUtils.safeRegexTest)(/login|sign-in|signin/, entry.name)); if (!entries.length) return; const filledForm = [...forms.values()].find(form => form.hasValues()); const focusedForm = [...forms.values()].find(form => form.hasFocus()); @@ -9411,7 +9411,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de const { ATTR_AUTOFILL, ATTR_INPUT_TYPE, - MAX_FORM_MUT_OBS_COUNT, MAX_INPUTS_PER_FORM } = _constants.constants; class Form { @@ -9460,29 +9459,6 @@ class Form { if (!entry.isIntersecting) this.removeTooltip(); } }); - this.mutObsCount = 0; - this.mutObsConfig = { - childList: true, - subtree: true - }; - this.mutObs = new MutationObserver(records => { - const anythingRemoved = records.some(record => record.removedNodes.length > 0); - if (anythingRemoved) { - // Must check for inputs because a parent may be removed and not show up in record.removedNodes - if ([...this.inputs.all].some(input => !input.isConnected)) { - // If any known input has been removed from the DOM, reanalyze the whole form - window.requestIdleCallback(() => { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - }); - this.mutObsCount++; - // If the form mutates too much, disconnect to avoid performance issues - if (this.mutObsCount >= MAX_FORM_MUT_OBS_COUNT) { - this.mutObs.disconnect(); - } - } - } - }); // This ensures we fire the handler again if the form is changed this.addListener(form, 'input', () => { @@ -9492,7 +9468,6 @@ class Form { } }); this.categorizeInputs(); - this.mutObs.observe(this.form, this.mutObsConfig); this.logFormInfo(); if (shouldAutoprompt) { this.promptLoginIfNeeded(); @@ -9577,7 +9552,7 @@ class Form { const probableField = hiddenFields.find(field => { const regex = new RegExp('email|' + this.matching.getDDGMatcherRegex('username')?.source); const attributeText = field.id + ' ' + field.name; - return regex?.test(attributeText); + return (0, _autofillUtils.safeRegexTest)(regex, attributeText); }); if (probableField?.value) { formValues.credentials.username = probableField.value; @@ -9723,7 +9698,6 @@ class Form { this.removeAllDecorations(); this.removeTooltip(); this.forgetAllInputs(); - this.mutObs.disconnect(); this.matching.clear(); this.intObs = null; } @@ -9805,13 +9779,6 @@ class Form { return this; } - // When new inputs are added after the initial scan, reanalyze the whole form - if (this.initialScanComplete) { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - return this; - } - // Nothing to do with 1-character fields if (input.maxLength === 1) return this; this.inputs.all.add(input); @@ -10317,15 +10284,15 @@ class FormAnalyzer { } = _ref; // If the string is empty or too long (noisy) do nothing if (!string || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return this; - const matchesLogin = /current.?password/i.test(string) || this.matching.getDDGMatcherRegex('loginRegex')?.test(string) || this.matching.getDDGMatcherRegex('resetPasswordLink')?.test(string); + const matchesLogin = (0, _autofillUtils.safeRegexTest)(/current.?password/i, string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string); // Check explicitly for unified login/signup forms - if (shouldCheckUnifiedForm && matchesLogin && this.matching.getDDGMatcherRegex('conservativeSignupRegex')?.test(string)) { + if (shouldCheckUnifiedForm && matchesLogin && (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), string)) { this.increaseHybridSignal(strength, signalType); return this; } const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? 'conservativeSignupRegex' : 'signupRegex'); - const matchesSignup = /new.?password/i.test(string) || signupRegexToUse?.test(string); + const matchesSignup = (0, _autofillUtils.safeRegexTest)(/new.?password/i, string) || (0, _autofillUtils.safeRegexTest)(signupRegexToUse, string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside if (shouldFlip) { @@ -10353,8 +10320,8 @@ class FormAnalyzer { } evaluateUrl() { const path = window.location.pathname; - const matchesLogin = this.matching.getDDGMatcherRegex('loginRegex')?.test(path); - const matchesSignup = this.matching.getDDGMatcherRegex('conservativeSignupRegex')?.test(path); + const matchesLogin = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), path); + const matchesSignup = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), path); // If the url matches both, do nothing: the signal is probably confounding if (matchesLogin && matchesSignup) return; @@ -10439,10 +10406,10 @@ class FormAnalyzer { let shouldFlip = true; let strength = 1; // Don't flip forgotten password links - if (this.matching.getDDGMatcherRegex('resetPasswordLink')?.test(string)) { + if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string)) { shouldFlip = false; strength = 3; - } else if (this.matching.getDDGMatcherRegex('loginProvidersRegex')?.test(string)) { + } else if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginProvidersRegex'), string)) { // Don't flip login providers links shouldFlip = false; } @@ -10521,7 +10488,7 @@ class FormAnalyzer { name, value } = _ref2; - return /(credit|payment).?card/i.test(`${name}=${value}`); + return (0, _autofillUtils.safeRegexTest)(/(credit|payment).?card/i, `${name}=${value}`); }); if (hasCCAttribute) { this._isCCForm = true; @@ -12096,15 +12063,15 @@ const matchingConfiguration = exports.matchingConfiguration = { }, expirationMonth: { match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, - skip: /mm[/\s.\-_—–]/iu + skip: /mm[/\s.\-_—–]|check/iu }, expirationYear: { match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, - skip: /mm[/\s.\-_—–]/iu + skip: /mm[/\s.\-_—–]|check/iu }, expiration: { match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, - skip: /invalid|^dd\//iu + skip: /invalid|^dd\/|check/iu }, firstName: { match: /(first|given|fore).?name|\bnome/iu, @@ -12811,7 +12778,7 @@ class Matching { matched: false }; } - if (notRegex.test(elementString)) { + if ((0, _autofillUtils.safeRegexTest)(notRegex, elementString)) { return { ...result, matched: false, @@ -12830,7 +12797,7 @@ class Matching { matched: false }; } - if (skipRegex.test(elementString)) { + if ((0, _autofillUtils.safeRegexTest)(skipRegex, elementString)) { return { ...result, matched: false, @@ -12840,7 +12807,7 @@ class Matching { } // if the `match` regex fails, moves onto the next string - if (!matchRexExp.test(elementString)) { + if (!(0, _autofillUtils.safeRegexTest)(matchRexExp, elementString)) { continue; } @@ -12891,7 +12858,7 @@ class Matching { for (let stringName of stringsToMatch) { let elementString = this.activeElementStrings[stringName]; if (!elementString) continue; - if (regex.test(elementString)) { + if ((0, _autofillUtils.safeRegexTest)(regex, elementString)) { return { ...defaultResult, matched: true, @@ -13869,8 +13836,13 @@ class DefaultScanner { if (this.stopped) return; const parentForm = this.getParentForm(input); if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { - // We've met the form, add the input - this.forms.get(parentForm)?.addInput(input); + const foundForm = this.forms.get(parentForm); + // We've met the form, add the input provided it's below the max input limit + if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM) { + foundForm.addInput(input); + } else { + this.stopScanner('The form has too many inputs, destroying.'); + } return; } @@ -15048,8 +15020,13 @@ class HTMLTooltipUIController extends _UIController.UIController { super(); this._options = options; this._htmlTooltipOptions = Object.assign({}, _HTMLTooltip.defaultOptions, htmlTooltipOptions); - window.addEventListener('pointerdown', this, true); - window.addEventListener('pointerup', this, true); + // Use pointerup to mimic native click behaviour when we're in the top-frame webview + if (options.device.globalConfig.isTopFrame) { + window.addEventListener('pointerup', this, true); + } else { + // Pointerdown is needed here to avoid self-closing modals disappearing because this even happens in the page + window.addEventListener('pointerdown', this, true); + } } _activeInput; _activeInputOriginalAutocomplete; @@ -15214,9 +15191,7 @@ class HTMLTooltipUIController extends _UIController.UIController { // @ts-ignore if (e.target.nodeName === 'DDG-AUTOFILL') { - e.preventDefault(); - e.stopImmediatePropagation(); - // Ignore pointer down events, we'll handle them on pointer up + this._handleClickInTooltip(e); } else { this.removeTooltip().catch(e => { console.error('error removing tooltip', e); @@ -15232,14 +15207,17 @@ class HTMLTooltipUIController extends _UIController.UIController { // @ts-ignore if (e.target.nodeName === 'DDG-AUTOFILL') { - e.preventDefault(); - e.stopImmediatePropagation(); - const isMainMouseButton = e.button === 0; - if (!isMainMouseButton) return; - const activeTooltip = this.getActiveTooltip(); - activeTooltip?.dispatchClick(); + this._handleClickInTooltip(e); } } + _handleClickInTooltip(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + const isMainMouseButton = e.button === 0; + if (!isMainMouseButton) return; + const activeTooltip = this.getActiveTooltip(); + activeTooltip?.dispatchClick(); + } async removeTooltip(_via) { this._htmlTooltipOptions.remove(); if (this._activeTooltip) { @@ -15867,13 +15845,16 @@ exports.isLocalNetwork = isLocalNetwork; exports.isPotentiallyViewable = void 0; exports.isValidTLD = isValidTLD; exports.logPerformance = logPerformance; -exports.setValue = exports.sendAndWaitForAnswer = exports.safeExecute = exports.removeInlineStyles = exports.notifyWebApp = void 0; +exports.safeExecute = exports.removeInlineStyles = exports.notifyWebApp = void 0; +exports.safeRegexTest = safeRegexTest; +exports.setValue = exports.sendAndWaitForAnswer = void 0; exports.shouldLog = shouldLog; exports.shouldLogPerformance = shouldLogPerformance; exports.truncateFromMiddle = truncateFromMiddle; exports.wasAutofilledByChrome = void 0; exports.whenIdle = whenIdle; var _matching = require("./Form/matching.js"); +var _constants = require("./constants.js"); const SIGN_IN_MSG = exports.SIGN_IN_MSG = { signMeIn: true }; @@ -16226,15 +16207,15 @@ const isLikelyASubmitButton = (el, matching) => { // is explicitly set as "submit" el.getAttribute('name') === 'submit') && // is called "submit" - !matching.getDDGMatcherRegex('submitButtonUnlikelyRegex')?.test(text + ' ' + ariaLabel)) return true; - return (/primary|submit/i.test(el.className) || + !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel)) return true; + return (safeRegexTest(/primary|submit/i, el.className) || // has high-signal submit classes - /submit/i.test(dataTestId) || matching.getDDGMatcherRegex('submitButtonRegex')?.test(text) || + safeRegexTest(/submit/i, dataTestId) || safeRegexTest(matching.getDDGMatcherRegex('submitButtonRegex'), text) || // has high-signal text - el.offsetHeight * el.offsetWidth >= 10000 && !/secondary/i.test(el.className) // it's a large element 250x40px + el.offsetHeight * el.offsetWidth >= 10000 && !safeRegexTest(/secondary/i, el.className) // it's a large element 250x40px ) && el.offsetHeight * el.offsetWidth >= 2000 && // it's not a very small button like inline links and such - !matching.getDDGMatcherRegex('submitButtonUnlikelyRegex')?.test(text + ' ' + ariaLabel); + !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel); }; /** @@ -16245,9 +16226,9 @@ const isLikelyASubmitButton = (el, matching) => { exports.isLikelyASubmitButton = isLikelyASubmitButton; const buttonMatchesFormType = (el, formObj) => { if (formObj.isLogin) { - return !/sign.?up|register|join/i.test(el.textContent || ''); + return !safeRegexTest(/sign.?up|register|join/i, el.textContent || ''); } else if (formObj.isSignup) { - return !/(log|sign).?([io])n/i.test(el.textContent || ''); + return !safeRegexTest(/(log|sign).?([io])n/i, el.textContent || ''); } else { return true; } @@ -16408,7 +16389,18 @@ function isFormLikelyToBeUsedAsPageWrapper(form) { return formChildrenPercentage > 50; } -},{"./Form/matching.js":43}],62:[function(require,module,exports){ +/** + * Wrapper around RegExp.test that safeguard against checking huge strings + * @param {RegExp | undefined} regex + * @param {String} string + * @returns {boolean} + */ +function safeRegexTest(regex, string) { + if (!string || !regex || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return false; + return regex.test(string); +} + +},{"./Form/matching.js":43,"./constants.js":64}],62:[function(require,module,exports){ "use strict"; require("./requestIdleCallback.js"); diff --git a/node_modules/@duckduckgo/autofill/dist/autofill.js b/node_modules/@duckduckgo/autofill/dist/autofill.js index 8fa1a238c593..e9bd2867c0b8 100644 --- a/node_modules/@duckduckgo/autofill/dist/autofill.js +++ b/node_modules/@duckduckgo/autofill/dist/autofill.js @@ -5317,7 +5317,7 @@ function initFormSubmissionsApi(forms, matching) { const button = /** @type HTMLElement */event.target?.closest(selector); if (!button) return; const text = (0, _autofillUtils.getTextShallow)(button) || (0, _labelUtil.extractElementStrings)(button).join(' '); - const hasRelevantText = matching.getDDGMatcherRegex('submitButtonRegex')?.test(text); + const hasRelevantText = (0, _autofillUtils.safeRegexTest)(matching.getDDGMatcherRegex('submitButtonRegex'), text); if (hasRelevantText && text.length < 25) { // check if there's a form with values const filledForm = [...forms.values()].find(form => form.hasValues()); @@ -5342,7 +5342,7 @@ function initFormSubmissionsApi(forms, matching) { const observer = new PerformanceObserver(list => { const entries = list.getEntries().filter(entry => // @ts-ignore why does TS not know about `entry.initiatorType`? - ['fetch', 'xmlhttprequest'].includes(entry.initiatorType) && /login|sign-in|signin/.test(entry.name)); + ['fetch', 'xmlhttprequest'].includes(entry.initiatorType) && (0, _autofillUtils.safeRegexTest)(/login|sign-in|signin/, entry.name)); if (!entries.length) return; const filledForm = [...forms.values()].find(form => form.hasValues()); const focusedForm = [...forms.values()].find(form => form.hasFocus()); @@ -5466,7 +5466,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de const { ATTR_AUTOFILL, ATTR_INPUT_TYPE, - MAX_FORM_MUT_OBS_COUNT, MAX_INPUTS_PER_FORM } = _constants.constants; class Form { @@ -5515,29 +5514,6 @@ class Form { if (!entry.isIntersecting) this.removeTooltip(); } }); - this.mutObsCount = 0; - this.mutObsConfig = { - childList: true, - subtree: true - }; - this.mutObs = new MutationObserver(records => { - const anythingRemoved = records.some(record => record.removedNodes.length > 0); - if (anythingRemoved) { - // Must check for inputs because a parent may be removed and not show up in record.removedNodes - if ([...this.inputs.all].some(input => !input.isConnected)) { - // If any known input has been removed from the DOM, reanalyze the whole form - window.requestIdleCallback(() => { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - }); - this.mutObsCount++; - // If the form mutates too much, disconnect to avoid performance issues - if (this.mutObsCount >= MAX_FORM_MUT_OBS_COUNT) { - this.mutObs.disconnect(); - } - } - } - }); // This ensures we fire the handler again if the form is changed this.addListener(form, 'input', () => { @@ -5547,7 +5523,6 @@ class Form { } }); this.categorizeInputs(); - this.mutObs.observe(this.form, this.mutObsConfig); this.logFormInfo(); if (shouldAutoprompt) { this.promptLoginIfNeeded(); @@ -5632,7 +5607,7 @@ class Form { const probableField = hiddenFields.find(field => { const regex = new RegExp('email|' + this.matching.getDDGMatcherRegex('username')?.source); const attributeText = field.id + ' ' + field.name; - return regex?.test(attributeText); + return (0, _autofillUtils.safeRegexTest)(regex, attributeText); }); if (probableField?.value) { formValues.credentials.username = probableField.value; @@ -5778,7 +5753,6 @@ class Form { this.removeAllDecorations(); this.removeTooltip(); this.forgetAllInputs(); - this.mutObs.disconnect(); this.matching.clear(); this.intObs = null; } @@ -5860,13 +5834,6 @@ class Form { return this; } - // When new inputs are added after the initial scan, reanalyze the whole form - if (this.initialScanComplete) { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - return this; - } - // Nothing to do with 1-character fields if (input.maxLength === 1) return this; this.inputs.all.add(input); @@ -6372,15 +6339,15 @@ class FormAnalyzer { } = _ref; // If the string is empty or too long (noisy) do nothing if (!string || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return this; - const matchesLogin = /current.?password/i.test(string) || this.matching.getDDGMatcherRegex('loginRegex')?.test(string) || this.matching.getDDGMatcherRegex('resetPasswordLink')?.test(string); + const matchesLogin = (0, _autofillUtils.safeRegexTest)(/current.?password/i, string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string); // Check explicitly for unified login/signup forms - if (shouldCheckUnifiedForm && matchesLogin && this.matching.getDDGMatcherRegex('conservativeSignupRegex')?.test(string)) { + if (shouldCheckUnifiedForm && matchesLogin && (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), string)) { this.increaseHybridSignal(strength, signalType); return this; } const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? 'conservativeSignupRegex' : 'signupRegex'); - const matchesSignup = /new.?password/i.test(string) || signupRegexToUse?.test(string); + const matchesSignup = (0, _autofillUtils.safeRegexTest)(/new.?password/i, string) || (0, _autofillUtils.safeRegexTest)(signupRegexToUse, string); // In some cases a login match means the login is somewhere else, i.e. when a link points outside if (shouldFlip) { @@ -6408,8 +6375,8 @@ class FormAnalyzer { } evaluateUrl() { const path = window.location.pathname; - const matchesLogin = this.matching.getDDGMatcherRegex('loginRegex')?.test(path); - const matchesSignup = this.matching.getDDGMatcherRegex('conservativeSignupRegex')?.test(path); + const matchesLogin = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), path); + const matchesSignup = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), path); // If the url matches both, do nothing: the signal is probably confounding if (matchesLogin && matchesSignup) return; @@ -6494,10 +6461,10 @@ class FormAnalyzer { let shouldFlip = true; let strength = 1; // Don't flip forgotten password links - if (this.matching.getDDGMatcherRegex('resetPasswordLink')?.test(string)) { + if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string)) { shouldFlip = false; strength = 3; - } else if (this.matching.getDDGMatcherRegex('loginProvidersRegex')?.test(string)) { + } else if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginProvidersRegex'), string)) { // Don't flip login providers links shouldFlip = false; } @@ -6576,7 +6543,7 @@ class FormAnalyzer { name, value } = _ref2; - return /(credit|payment).?card/i.test(`${name}=${value}`); + return (0, _autofillUtils.safeRegexTest)(/(credit|payment).?card/i, `${name}=${value}`); }); if (hasCCAttribute) { this._isCCForm = true; @@ -8151,15 +8118,15 @@ const matchingConfiguration = exports.matchingConfiguration = { }, expirationMonth: { match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, - skip: /mm[/\s.\-_—–]/iu + skip: /mm[/\s.\-_—–]|check/iu }, expirationYear: { match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, - skip: /mm[/\s.\-_—–]/iu + skip: /mm[/\s.\-_—–]|check/iu }, expiration: { match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, - skip: /invalid|^dd\//iu + skip: /invalid|^dd\/|check/iu }, firstName: { match: /(first|given|fore).?name|\bnome/iu, @@ -8866,7 +8833,7 @@ class Matching { matched: false }; } - if (notRegex.test(elementString)) { + if ((0, _autofillUtils.safeRegexTest)(notRegex, elementString)) { return { ...result, matched: false, @@ -8885,7 +8852,7 @@ class Matching { matched: false }; } - if (skipRegex.test(elementString)) { + if ((0, _autofillUtils.safeRegexTest)(skipRegex, elementString)) { return { ...result, matched: false, @@ -8895,7 +8862,7 @@ class Matching { } // if the `match` regex fails, moves onto the next string - if (!matchRexExp.test(elementString)) { + if (!(0, _autofillUtils.safeRegexTest)(matchRexExp, elementString)) { continue; } @@ -8946,7 +8913,7 @@ class Matching { for (let stringName of stringsToMatch) { let elementString = this.activeElementStrings[stringName]; if (!elementString) continue; - if (regex.test(elementString)) { + if ((0, _autofillUtils.safeRegexTest)(regex, elementString)) { return { ...defaultResult, matched: true, @@ -9924,8 +9891,13 @@ class DefaultScanner { if (this.stopped) return; const parentForm = this.getParentForm(input); if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { - // We've met the form, add the input - this.forms.get(parentForm)?.addInput(input); + const foundForm = this.forms.get(parentForm); + // We've met the form, add the input provided it's below the max input limit + if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM) { + foundForm.addInput(input); + } else { + this.stopScanner('The form has too many inputs, destroying.'); + } return; } @@ -11103,8 +11075,13 @@ class HTMLTooltipUIController extends _UIController.UIController { super(); this._options = options; this._htmlTooltipOptions = Object.assign({}, _HTMLTooltip.defaultOptions, htmlTooltipOptions); - window.addEventListener('pointerdown', this, true); - window.addEventListener('pointerup', this, true); + // Use pointerup to mimic native click behaviour when we're in the top-frame webview + if (options.device.globalConfig.isTopFrame) { + window.addEventListener('pointerup', this, true); + } else { + // Pointerdown is needed here to avoid self-closing modals disappearing because this even happens in the page + window.addEventListener('pointerdown', this, true); + } } _activeInput; _activeInputOriginalAutocomplete; @@ -11269,9 +11246,7 @@ class HTMLTooltipUIController extends _UIController.UIController { // @ts-ignore if (e.target.nodeName === 'DDG-AUTOFILL') { - e.preventDefault(); - e.stopImmediatePropagation(); - // Ignore pointer down events, we'll handle them on pointer up + this._handleClickInTooltip(e); } else { this.removeTooltip().catch(e => { console.error('error removing tooltip', e); @@ -11287,14 +11262,17 @@ class HTMLTooltipUIController extends _UIController.UIController { // @ts-ignore if (e.target.nodeName === 'DDG-AUTOFILL') { - e.preventDefault(); - e.stopImmediatePropagation(); - const isMainMouseButton = e.button === 0; - if (!isMainMouseButton) return; - const activeTooltip = this.getActiveTooltip(); - activeTooltip?.dispatchClick(); + this._handleClickInTooltip(e); } } + _handleClickInTooltip(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + const isMainMouseButton = e.button === 0; + if (!isMainMouseButton) return; + const activeTooltip = this.getActiveTooltip(); + activeTooltip?.dispatchClick(); + } async removeTooltip(_via) { this._htmlTooltipOptions.remove(); if (this._activeTooltip) { @@ -11922,13 +11900,16 @@ exports.isLocalNetwork = isLocalNetwork; exports.isPotentiallyViewable = void 0; exports.isValidTLD = isValidTLD; exports.logPerformance = logPerformance; -exports.setValue = exports.sendAndWaitForAnswer = exports.safeExecute = exports.removeInlineStyles = exports.notifyWebApp = void 0; +exports.safeExecute = exports.removeInlineStyles = exports.notifyWebApp = void 0; +exports.safeRegexTest = safeRegexTest; +exports.setValue = exports.sendAndWaitForAnswer = void 0; exports.shouldLog = shouldLog; exports.shouldLogPerformance = shouldLogPerformance; exports.truncateFromMiddle = truncateFromMiddle; exports.wasAutofilledByChrome = void 0; exports.whenIdle = whenIdle; var _matching = require("./Form/matching.js"); +var _constants = require("./constants.js"); const SIGN_IN_MSG = exports.SIGN_IN_MSG = { signMeIn: true }; @@ -12281,15 +12262,15 @@ const isLikelyASubmitButton = (el, matching) => { // is explicitly set as "submit" el.getAttribute('name') === 'submit') && // is called "submit" - !matching.getDDGMatcherRegex('submitButtonUnlikelyRegex')?.test(text + ' ' + ariaLabel)) return true; - return (/primary|submit/i.test(el.className) || + !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel)) return true; + return (safeRegexTest(/primary|submit/i, el.className) || // has high-signal submit classes - /submit/i.test(dataTestId) || matching.getDDGMatcherRegex('submitButtonRegex')?.test(text) || + safeRegexTest(/submit/i, dataTestId) || safeRegexTest(matching.getDDGMatcherRegex('submitButtonRegex'), text) || // has high-signal text - el.offsetHeight * el.offsetWidth >= 10000 && !/secondary/i.test(el.className) // it's a large element 250x40px + el.offsetHeight * el.offsetWidth >= 10000 && !safeRegexTest(/secondary/i, el.className) // it's a large element 250x40px ) && el.offsetHeight * el.offsetWidth >= 2000 && // it's not a very small button like inline links and such - !matching.getDDGMatcherRegex('submitButtonUnlikelyRegex')?.test(text + ' ' + ariaLabel); + !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel); }; /** @@ -12300,9 +12281,9 @@ const isLikelyASubmitButton = (el, matching) => { exports.isLikelyASubmitButton = isLikelyASubmitButton; const buttonMatchesFormType = (el, formObj) => { if (formObj.isLogin) { - return !/sign.?up|register|join/i.test(el.textContent || ''); + return !safeRegexTest(/sign.?up|register|join/i, el.textContent || ''); } else if (formObj.isSignup) { - return !/(log|sign).?([io])n/i.test(el.textContent || ''); + return !safeRegexTest(/(log|sign).?([io])n/i, el.textContent || ''); } else { return true; } @@ -12463,7 +12444,18 @@ function isFormLikelyToBeUsedAsPageWrapper(form) { return formChildrenPercentage > 50; } -},{"./Form/matching.js":33}],52:[function(require,module,exports){ +/** + * Wrapper around RegExp.test that safeguard against checking huge strings + * @param {RegExp | undefined} regex + * @param {String} string + * @returns {boolean} + */ +function safeRegexTest(regex, string) { + if (!string || !regex || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return false; + return regex.test(string); +} + +},{"./Form/matching.js":33,"./constants.js":54}],52:[function(require,module,exports){ "use strict"; require("./requestIdleCallback.js"); diff --git a/package-lock.json b/package-lock.json index 2a8264ae708a..f648d175a829 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "@duckduckgo/autoconsent": "^5.1.0", - "@duckduckgo/autofill": "github:duckduckgo/duckduckgo-autofill#8.4.1", + "@duckduckgo/autofill": "github:duckduckgo/duckduckgo-autofill#8.4.2", "@duckduckgo/content-scope-scripts": "github:duckduckgo/content-scope-scripts#4.39.0", "@duckduckgo/privacy-dashboard": "github:duckduckgo/privacy-dashboard#1.4.0", "@duckduckgo/privacy-reference-tests": "github:duckduckgo/privacy-reference-tests#1692081744" @@ -64,7 +64,7 @@ "integrity": "sha512-/ZUdNt+FLhtT40f53Pl/TwOLX1Rr4vCyzgDXQjtXBHF7vSaQJLRdkDkiEm4P24HAxNbg+WGeleJUiIEyQgfp2A==" }, "node_modules/@duckduckgo/autofill": { - "resolved": "git+ssh://git@github.com/duckduckgo/duckduckgo-autofill.git#55466f10a339843cb79a27af62d5d61c030740b2", + "resolved": "git+ssh://git@github.com/duckduckgo/duckduckgo-autofill.git#6dd7d696d4e666cedb2f1890a46fe53615226646", "hasInstallScript": true, "license": "Apache-2.0" }, diff --git a/package.json b/package.json index c67857780cea..5fb01eb12b8e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@duckduckgo/autoconsent": "^5.1.0", - "@duckduckgo/autofill": "github:duckduckgo/duckduckgo-autofill#8.4.1", + "@duckduckgo/autofill": "github:duckduckgo/duckduckgo-autofill#8.4.2", "@duckduckgo/content-scope-scripts": "github:duckduckgo/content-scope-scripts#4.39.0", "@duckduckgo/privacy-dashboard": "github:duckduckgo/privacy-dashboard#1.4.0", "@duckduckgo/privacy-reference-tests": "github:duckduckgo/privacy-reference-tests#1692081744"