diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 9670360cd..98a50b071 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -7339,6 +7339,10 @@ class AndroidInterface extends _InterfacePrototype.default { async isEnabled() { return (0, _autofillUtils.autofillEnabled)(this.globalConfig, _appleUtils.processConfig); } + /** + * @returns {Promise} + */ + async getAlias() { const { @@ -7639,20 +7643,9 @@ class AppleDeviceInterface extends _InterfacePrototype.default { if ('configType' in response) { this.selectedDetail(response.data, response.configType); } else if ('stop' in response) { - var _this$activeForm, _this$activeForm$acti; - - // Let input handlers know we've stopped autofilling - (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + await this.onFinishedAutofill(); } else if ('stateChange' in response) { - var _this$activeForm2, _this$activeForm3; - - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed - - await this.refreshData(); // Add correct icons and behaviour - - (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + await this.updateForStateChange(); } }); } @@ -7802,13 +7795,13 @@ class AppleDeviceInterface extends _InterfacePrototype.default { } getCurrentInputType() { - var _this$activeForm4; + var _this$activeForm; const topContextData = this.getTopContextData(); - return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm4 = this.activeForm) === null || _this$activeForm4 === void 0 ? void 0 : _this$activeForm4.activeInput); + return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : _this$activeForm.activeInput); } /** - * @returns {Promise} + * @returns {Promise} */ @@ -7817,9 +7810,10 @@ class AppleDeviceInterface extends _InterfacePrototype.default { alias } = await this.deviceApi.request(new _additionalDeviceApiCalls.GetAlias({ requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp + shouldConsumeAliasIfProvided: !this.globalConfig.isApp, + isIncontextSignupAvailable: this.inContextSignup.isAvailable() })); - return (0, _autofillUtils.formatDuckAddress)(alias); + return alias ? (0, _autofillUtils.formatDuckAddress)(alias) : alias; } addLogoutListener(handler) { @@ -8919,8 +8913,6 @@ class InterfacePrototype { if (this.globalConfig.isMobileApp && inputType === 'identities.emailAddress') { this.getAlias().then(alias => { - var _form$activeInput; - if (alias) { form.autofillEmail(alias); /** @@ -8929,7 +8921,16 @@ class InterfacePrototype { */ this.emailProtection.storeReceived(alias); - } else (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } else { + var _form$activeInput; + + (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } // Update data from native-side in case the `getAlias` call + // has included a successful in-context signup + + + this.updateForStateChange(); + this.onFinishedAutofill(); }); return; } @@ -9074,6 +9075,25 @@ class InterfacePrototype { return (_this$uiController4 = this.uiController) === null || _this$uiController4 === void 0 ? void 0 : (_this$uiController4$r = _this$uiController4.removeTooltip) === null || _this$uiController4$r === void 0 ? void 0 : _this$uiController4$r.call(_this$uiController4, 'interface'); } + onFinishedAutofill() { + var _this$activeForm, _this$activeForm$acti; + + // Let input handlers know we've stopped autofilling + (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + } + + async updateForStateChange() { + var _this$activeForm2, _this$activeForm3; + + // Remove decorations before refreshing data to make sure we + // remove the currently set icons + (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed + + await this.refreshData(); // Add correct icons and behaviour + + (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + } + async refreshData() { var _this$inContextSignup; @@ -9231,12 +9251,12 @@ class InterfacePrototype { return false; } /** - * @returns {Promise} + * @returns {Promise} */ async getAlias() { - return null; + return undefined; } // PM endpoints @@ -10571,7 +10591,7 @@ class Form { * Adds event listeners and keeps track of them for subsequent removal * @param {HTMLElement} el * @param {Event['type']} type - * @param {() => void} fn + * @param {(Event) => void} fn * @param {AddEventListenerOptions} [opts] */ @@ -10643,7 +10663,11 @@ class Form { }); } }); - } // We need click coordinates to position the tooltip when the field is in an iframe + } + /** + * @param {PointerEvent} e + * @returns {{ x: number; y: number; } | undefined} + */ function getMainClickCoords(e) { @@ -10654,22 +10678,54 @@ class Form { x: e.clientX, y: e.clientY }; + } + /** + * @param {Event} e + * @param {WeakMap} storedClickCoords + * @returns {{ x: number; y: number; } | null} + */ + + + function getClickCoords(e, storedClickCoords) { + // Get click co-ordinates for pointer events + // We need click coordinates to position the tooltip when the field is in an iframe + if (e.type === 'pointerdown') { + return getMainClickCoords( + /** @type {PointerEvent} */ + e) || null; + } // Reuse a previous click co-ordinates if they exist for this element + + + const click = storedClickCoords.get(input); + storedClickCoords.delete(input); + return click || null; } // Store the click to a label so we can use the click when the field is focused // Needed to handle label clicks when the form is in an iframe - let storedClick = new WeakMap(); + let storedClickCoords = new WeakMap(); let timeout = null; + /** + * @param {PointerEvent} e + */ const handlerLabel = e => { + var _e$target, _e$target$closest; + // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = e.target.closest('label').control; + const control = + /** @type HTMLElement */ + (_e$target = e.target) === null || _e$target === void 0 ? void 0 : (_e$target$closest = _e$target.closest('label')) === null || _e$target$closest === void 0 ? void 0 : _e$target$closest.control; if (!control) return; - storedClick.set(control, getMainClickCoords(e)); + + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)); + } + clearTimeout(timeout); // Remove the stored click if the timer expires timeout = setTimeout(() => { - storedClick = new WeakMap(); + storedClickCoords = new WeakMap(); }, 1000); }; @@ -10683,17 +10739,13 @@ class Form { const isLabel = e.target instanceof HTMLLabelElement; const input = isLabel ? e.target.control : e.target; if (!input || !this.inputs.all.has(input)) return; - let click = null; if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; // Checks for pointerdown event. Needed for positioning when fields are within an iframe + if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; + const clickCoords = getClickCoords(e, storedClickCoords); if (e.type === 'pointerdown') { - click = getMainClickCoords(e); - if (!click) return; - } else if (storedClick) { - // Reuse a previous click if one exists for this element - click = storedClick.get(input); - storedClick.delete(input); + // Only allow real user clicks with co-ordinates through + if (!e.isTrusted || !clickCoords) return; } if (this.shouldOpenTooltip(e, input)) { @@ -10711,7 +10763,7 @@ class Form { this.device.attachTooltip({ form: this, input: input, - click: click, + click: clickCoords, trigger: 'userInitiated', triggerMetaData: { // An 'icon' click is very different to a field click or focus. @@ -10768,7 +10820,7 @@ class Form { return true; } - if (this.device.globalConfig.isExtension) { + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { // Don't open the tooltip on input focus whenever it's showing in-context signup if (isIncontextSignupAvailable) return false; } @@ -14659,15 +14711,21 @@ class InContextSignup { await ((_this$device$uiContro = this.device.uiController) === null || _this$device$uiContro === void 0 ? void 0 : _this$device$uiContro.removeTooltip('stateChange')); // Make sure the input doesn't have focus so we can focus on it again const activeInput = (_this$device$activeFo = this.device.activeForm) === null || _this$device$activeFo === void 0 ? void 0 : _this$device$activeFo.activeInput; - activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Focus on the active input to open the tooltip + activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Select the active input to open the tooltip - const focusActiveInput = () => { + const selectActiveInput = () => { activeInput === null || activeInput === void 0 ? void 0 : activeInput.focus(); }; - document.hasFocus() ? focusActiveInput() : document.addEventListener('visibilitychange', focusActiveInput, { - once: true - }); + if (document.hasFocus()) { + selectActiveInput(); + } else { + document.addEventListener('visibilitychange', () => { + selectActiveInput(); + }, { + once: true + }); + } } isPermanentlyDismissed() { @@ -19097,14 +19155,15 @@ exports.autofillFeatureTogglesSchema = autofillFeatureTogglesSchema; const getAliasParamsSchema = _zod.z.object({ requiresUserPermission: _zod.z.boolean(), - shouldConsumeAliasIfProvided: _zod.z.boolean() + shouldConsumeAliasIfProvided: _zod.z.boolean(), + isIncontextSignupAvailable: _zod.z.boolean().optional() }); exports.getAliasParamsSchema = getAliasParamsSchema; const getAliasResultSchema = _zod.z.object({ success: _zod.z.object({ - alias: _zod.z.string() + alias: _zod.z.string().optional() }) }); diff --git a/dist/autofill.js b/dist/autofill.js index 743940a67..951d6f80d 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -3663,6 +3663,10 @@ class AndroidInterface extends _InterfacePrototype.default { async isEnabled() { return (0, _autofillUtils.autofillEnabled)(this.globalConfig, _appleUtils.processConfig); } + /** + * @returns {Promise} + */ + async getAlias() { const { @@ -3963,20 +3967,9 @@ class AppleDeviceInterface extends _InterfacePrototype.default { if ('configType' in response) { this.selectedDetail(response.data, response.configType); } else if ('stop' in response) { - var _this$activeForm, _this$activeForm$acti; - - // Let input handlers know we've stopped autofilling - (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + await this.onFinishedAutofill(); } else if ('stateChange' in response) { - var _this$activeForm2, _this$activeForm3; - - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed - - await this.refreshData(); // Add correct icons and behaviour - - (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + await this.updateForStateChange(); } }); } @@ -4126,13 +4119,13 @@ class AppleDeviceInterface extends _InterfacePrototype.default { } getCurrentInputType() { - var _this$activeForm4; + var _this$activeForm; const topContextData = this.getTopContextData(); - return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm4 = this.activeForm) === null || _this$activeForm4 === void 0 ? void 0 : _this$activeForm4.activeInput); + return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : _this$activeForm.activeInput); } /** - * @returns {Promise} + * @returns {Promise} */ @@ -4141,9 +4134,10 @@ class AppleDeviceInterface extends _InterfacePrototype.default { alias } = await this.deviceApi.request(new _additionalDeviceApiCalls.GetAlias({ requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp + shouldConsumeAliasIfProvided: !this.globalConfig.isApp, + isIncontextSignupAvailable: this.inContextSignup.isAvailable() })); - return (0, _autofillUtils.formatDuckAddress)(alias); + return alias ? (0, _autofillUtils.formatDuckAddress)(alias) : alias; } addLogoutListener(handler) { @@ -5243,8 +5237,6 @@ class InterfacePrototype { if (this.globalConfig.isMobileApp && inputType === 'identities.emailAddress') { this.getAlias().then(alias => { - var _form$activeInput; - if (alias) { form.autofillEmail(alias); /** @@ -5253,7 +5245,16 @@ class InterfacePrototype { */ this.emailProtection.storeReceived(alias); - } else (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } else { + var _form$activeInput; + + (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } // Update data from native-side in case the `getAlias` call + // has included a successful in-context signup + + + this.updateForStateChange(); + this.onFinishedAutofill(); }); return; } @@ -5398,6 +5399,25 @@ class InterfacePrototype { return (_this$uiController4 = this.uiController) === null || _this$uiController4 === void 0 ? void 0 : (_this$uiController4$r = _this$uiController4.removeTooltip) === null || _this$uiController4$r === void 0 ? void 0 : _this$uiController4$r.call(_this$uiController4, 'interface'); } + onFinishedAutofill() { + var _this$activeForm, _this$activeForm$acti; + + // Let input handlers know we've stopped autofilling + (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + } + + async updateForStateChange() { + var _this$activeForm2, _this$activeForm3; + + // Remove decorations before refreshing data to make sure we + // remove the currently set icons + (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed + + await this.refreshData(); // Add correct icons and behaviour + + (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + } + async refreshData() { var _this$inContextSignup; @@ -5555,12 +5575,12 @@ class InterfacePrototype { return false; } /** - * @returns {Promise} + * @returns {Promise} */ async getAlias() { - return null; + return undefined; } // PM endpoints @@ -6895,7 +6915,7 @@ class Form { * Adds event listeners and keeps track of them for subsequent removal * @param {HTMLElement} el * @param {Event['type']} type - * @param {() => void} fn + * @param {(Event) => void} fn * @param {AddEventListenerOptions} [opts] */ @@ -6967,7 +6987,11 @@ class Form { }); } }); - } // We need click coordinates to position the tooltip when the field is in an iframe + } + /** + * @param {PointerEvent} e + * @returns {{ x: number; y: number; } | undefined} + */ function getMainClickCoords(e) { @@ -6978,22 +7002,54 @@ class Form { x: e.clientX, y: e.clientY }; + } + /** + * @param {Event} e + * @param {WeakMap} storedClickCoords + * @returns {{ x: number; y: number; } | null} + */ + + + function getClickCoords(e, storedClickCoords) { + // Get click co-ordinates for pointer events + // We need click coordinates to position the tooltip when the field is in an iframe + if (e.type === 'pointerdown') { + return getMainClickCoords( + /** @type {PointerEvent} */ + e) || null; + } // Reuse a previous click co-ordinates if they exist for this element + + + const click = storedClickCoords.get(input); + storedClickCoords.delete(input); + return click || null; } // Store the click to a label so we can use the click when the field is focused // Needed to handle label clicks when the form is in an iframe - let storedClick = new WeakMap(); + let storedClickCoords = new WeakMap(); let timeout = null; + /** + * @param {PointerEvent} e + */ const handlerLabel = e => { + var _e$target, _e$target$closest; + // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = e.target.closest('label').control; + const control = + /** @type HTMLElement */ + (_e$target = e.target) === null || _e$target === void 0 ? void 0 : (_e$target$closest = _e$target.closest('label')) === null || _e$target$closest === void 0 ? void 0 : _e$target$closest.control; if (!control) return; - storedClick.set(control, getMainClickCoords(e)); + + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)); + } + clearTimeout(timeout); // Remove the stored click if the timer expires timeout = setTimeout(() => { - storedClick = new WeakMap(); + storedClickCoords = new WeakMap(); }, 1000); }; @@ -7007,17 +7063,13 @@ class Form { const isLabel = e.target instanceof HTMLLabelElement; const input = isLabel ? e.target.control : e.target; if (!input || !this.inputs.all.has(input)) return; - let click = null; if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; // Checks for pointerdown event. Needed for positioning when fields are within an iframe + if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; + const clickCoords = getClickCoords(e, storedClickCoords); if (e.type === 'pointerdown') { - click = getMainClickCoords(e); - if (!click) return; - } else if (storedClick) { - // Reuse a previous click if one exists for this element - click = storedClick.get(input); - storedClick.delete(input); + // Only allow real user clicks with co-ordinates through + if (!e.isTrusted || !clickCoords) return; } if (this.shouldOpenTooltip(e, input)) { @@ -7035,7 +7087,7 @@ class Form { this.device.attachTooltip({ form: this, input: input, - click: click, + click: clickCoords, trigger: 'userInitiated', triggerMetaData: { // An 'icon' click is very different to a field click or focus. @@ -7092,7 +7144,7 @@ class Form { return true; } - if (this.device.globalConfig.isExtension) { + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { // Don't open the tooltip on input focus whenever it's showing in-context signup if (isIncontextSignupAvailable) return false; } @@ -10983,15 +11035,21 @@ class InContextSignup { await ((_this$device$uiContro = this.device.uiController) === null || _this$device$uiContro === void 0 ? void 0 : _this$device$uiContro.removeTooltip('stateChange')); // Make sure the input doesn't have focus so we can focus on it again const activeInput = (_this$device$activeFo = this.device.activeForm) === null || _this$device$activeFo === void 0 ? void 0 : _this$device$activeFo.activeInput; - activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Focus on the active input to open the tooltip + activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Select the active input to open the tooltip - const focusActiveInput = () => { + const selectActiveInput = () => { activeInput === null || activeInput === void 0 ? void 0 : activeInput.focus(); }; - document.hasFocus() ? focusActiveInput() : document.addEventListener('visibilitychange', focusActiveInput, { - once: true - }); + if (document.hasFocus()) { + selectActiveInput(); + } else { + document.addEventListener('visibilitychange', () => { + selectActiveInput(); + }, { + once: true + }); + } } isPermanentlyDismissed() { diff --git a/src/DeviceInterface/AndroidInterface.js b/src/DeviceInterface/AndroidInterface.js index 6f7bf927e..658f1f6aa 100644 --- a/src/DeviceInterface/AndroidInterface.js +++ b/src/DeviceInterface/AndroidInterface.js @@ -8,6 +8,9 @@ class AndroidInterface extends InterfacePrototype { return autofillEnabled(this.globalConfig, processConfig) } + /** + * @returns {Promise} + */ async getAlias () { const { alias } = await sendAndWaitForAnswer(() => { return window.EmailInterface.showTooltip() diff --git a/src/DeviceInterface/AppleDeviceInterface.js b/src/DeviceInterface/AppleDeviceInterface.js index 485976e7b..cafd30788 100644 --- a/src/DeviceInterface/AppleDeviceInterface.js +++ b/src/DeviceInterface/AppleDeviceInterface.js @@ -137,18 +137,9 @@ class AppleDeviceInterface extends InterfacePrototype { if ('configType' in response) { this.selectedDetail(response.data, response.configType) } else if ('stop' in response) { - // Let input handlers know we've stopped autofilling - this.activeForm?.activeInput?.dispatchEvent(new Event('mouseleave')) + await this.onFinishedAutofill() } else if ('stateChange' in response) { - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - this.activeForm?.removeAllDecorations() - - // Update for any state that may have changed - await this.refreshData() - - // Add correct icons and behaviour - this.activeForm?.recategorizeAllInputs() + await this.updateForStateChange() } }) } @@ -269,14 +260,15 @@ class AppleDeviceInterface extends InterfacePrototype { } /** - * @returns {Promise} + * @returns {Promise} */ async getAlias () { const {alias} = await this.deviceApi.request(new GetAlias({ requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp + shouldConsumeAliasIfProvided: !this.globalConfig.isApp, + isIncontextSignupAvailable: this.inContextSignup.isAvailable() })) - return formatDuckAddress(alias) + return alias ? formatDuckAddress(alias) : alias } addLogoutListener (handler) { diff --git a/src/DeviceInterface/InterfacePrototype.js b/src/DeviceInterface/InterfacePrototype.js index dbf2daae8..92ca56d5d 100644 --- a/src/DeviceInterface/InterfacePrototype.js +++ b/src/DeviceInterface/InterfacePrototype.js @@ -468,7 +468,14 @@ class InterfacePrototype { * Then later in the form submission we can compare the values */ this.emailProtection.storeReceived(alias) - } else form.activeInput?.focus() + } else { + form.activeInput?.focus() + } + + // Update data from native-side in case the `getAlias` call + // has included a successful in-context signup + this.updateForStateChange() + this.onFinishedAutofill() }) return } @@ -583,6 +590,23 @@ class InterfacePrototype { return this.uiController?.removeTooltip?.('interface') } + onFinishedAutofill () { + // Let input handlers know we've stopped autofilling + this.activeForm?.activeInput?.dispatchEvent(new Event('mouseleave')) + } + + async updateForStateChange () { + // Remove decorations before refreshing data to make sure we + // remove the currently set icons + this.activeForm?.removeAllDecorations() + + // Update for any state that may have changed + await this.refreshData() + + // Add correct icons and behaviour + this.activeForm?.recategorizeAllInputs() + } + async refreshData () { await this.inContextSignup?.refreshData() await this.settings.populateData() @@ -705,10 +729,10 @@ class InterfacePrototype { addLogoutListener (_fn) {} isDeviceSignedIn () { return false } /** - * @returns {Promise} + * @returns {Promise} */ async getAlias () { - return null + return undefined } // PM endpoints getAccounts () {} diff --git a/src/Form/Form.js b/src/Form/Form.js index 1ad16ef97..0632ed086 100644 --- a/src/Form/Form.js +++ b/src/Form/Form.js @@ -468,7 +468,7 @@ class Form { * Adds event listeners and keeps track of them for subsequent removal * @param {HTMLElement} el * @param {Event['type']} type - * @param {() => void} fn + * @param {(Event) => void} fn * @param {AddEventListenerOptions} [opts] */ addListener (el, type, fn, opts) { @@ -528,7 +528,10 @@ class Form { }) } - // We need click coordinates to position the tooltip when the field is in an iframe + /** + * @param {PointerEvent} e + * @returns {{ x: number; y: number; } | undefined} + */ function getMainClickCoords (e) { if (!e.isTrusted) return const isMainMouseButton = e.button === 0 @@ -539,19 +542,45 @@ class Form { } } + /** + * @param {Event} e + * @param {WeakMap} storedClickCoords + * @returns {{ x: number; y: number; } | null} + */ + function getClickCoords (e, storedClickCoords) { + // Get click co-ordinates for pointer events + // We need click coordinates to position the tooltip when the field is in an iframe + if (e.type === 'pointerdown') { + return getMainClickCoords(/** @type {PointerEvent} */ (e)) || null + } + + // Reuse a previous click co-ordinates if they exist for this element + const click = storedClickCoords.get(input) + storedClickCoords.delete(input) + return click || null + } + // Store the click to a label so we can use the click when the field is focused // Needed to handle label clicks when the form is in an iframe - let storedClick = new WeakMap() + let storedClickCoords = new WeakMap() let timeout = null + + /** + * @param {PointerEvent} e + */ const handlerLabel = (e) => { // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = e.target.closest('label').control + const control = /** @type HTMLElement */(e.target)?.closest('label')?.control if (!control) return - storedClick.set(control, getMainClickCoords(e)) + + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)) + } + clearTimeout(timeout) // Remove the stored click if the timer expires timeout = setTimeout(() => { - storedClick = new WeakMap() + storedClickCoords = new WeakMap() }, 1000) } @@ -566,20 +595,15 @@ class Form { const input = isLabel ? e.target.control : e.target if (!input || !this.inputs.all.has(input)) return - let click = null - if (wasAutofilledByChrome(input)) return if (!canBeInteractedWith(input)) return - // Checks for pointerdown event. Needed for positioning when fields are within an iframe + const clickCoords = getClickCoords(e, storedClickCoords) + if (e.type === 'pointerdown') { - click = getMainClickCoords(e) - if (!click) return - } else if (storedClick) { - // Reuse a previous click if one exists for this element - click = storedClick.get(input) - storedClick.delete(input) + // Only allow real user clicks with co-ordinates through + if (!e.isTrusted || !clickCoords) return } if (this.shouldOpenTooltip(e, input)) { @@ -601,7 +625,7 @@ class Form { this.device.attachTooltip({ form: this, input: input, - click: click, + click: clickCoords, trigger: 'userInitiated', triggerMetaData: { // An 'icon' click is very different to a field click or focus. @@ -654,7 +678,7 @@ class Form { return true } - if (this.device.globalConfig.isExtension) { + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { // Don't open the tooltip on input focus whenever it's showing in-context signup if (isIncontextSignupAvailable) return false } diff --git a/src/InContextSignup.js b/src/InContextSignup.js index b354cb146..4c53c98d9 100644 --- a/src/InContextSignup.js +++ b/src/InContextSignup.js @@ -54,13 +54,18 @@ export class InContextSignup { const activeInput = this.device.activeForm?.activeInput activeInput?.blur() - // Focus on the active input to open the tooltip - const focusActiveInput = () => { + // Select the active input to open the tooltip + const selectActiveInput = () => { activeInput?.focus() } - document.hasFocus() - ? focusActiveInput() - : document.addEventListener('visibilitychange', focusActiveInput, {once: true}) + + if (document.hasFocus()) { + selectActiveInput() + } else { + document.addEventListener('visibilitychange', () => { + selectActiveInput() + }, {once: true}) + } } isPermanentlyDismissed () { diff --git a/src/deviceApiCalls/__generated__/validators-ts.ts b/src/deviceApiCalls/__generated__/validators-ts.ts index af99b5c77..cad3a2b97 100644 --- a/src/deviceApiCalls/__generated__/validators-ts.ts +++ b/src/deviceApiCalls/__generated__/validators-ts.ts @@ -597,10 +597,11 @@ export interface AutofillFeatureToggles { export interface GetAliasParams { requiresUserPermission: boolean; shouldConsumeAliasIfProvided: boolean; + isIncontextSignupAvailable?: boolean; } export interface GetAliasResult { success: { - alias: string; + alias?: string; }; } /** diff --git a/src/deviceApiCalls/__generated__/validators.zod.js b/src/deviceApiCalls/__generated__/validators.zod.js index 12e7291f5..4411d9fa4 100644 --- a/src/deviceApiCalls/__generated__/validators.zod.js +++ b/src/deviceApiCalls/__generated__/validators.zod.js @@ -206,12 +206,13 @@ export const autofillFeatureTogglesSchema = z.object({ export const getAliasParamsSchema = z.object({ requiresUserPermission: z.boolean(), - shouldConsumeAliasIfProvided: z.boolean() + shouldConsumeAliasIfProvided: z.boolean(), + isIncontextSignupAvailable: z.boolean().optional() }); export const getAliasResultSchema = z.object({ success: z.object({ - alias: z.string() + alias: z.string().optional() }) }); diff --git a/src/deviceApiCalls/schemas/getAlias.params.json b/src/deviceApiCalls/schemas/getAlias.params.json index 3dbe420a2..15789b832 100644 --- a/src/deviceApiCalls/schemas/getAlias.params.json +++ b/src/deviceApiCalls/schemas/getAlias.params.json @@ -9,6 +9,9 @@ }, "shouldConsumeAliasIfProvided": { "type": "boolean" + }, + "isIncontextSignupAvailable": { + "type": "boolean" } }, "required": [ diff --git a/src/deviceApiCalls/schemas/getAlias.result.json b/src/deviceApiCalls/schemas/getAlias.result.json index d5c399047..5e34cd9d3 100644 --- a/src/deviceApiCalls/schemas/getAlias.result.json +++ b/src/deviceApiCalls/schemas/getAlias.result.json @@ -11,10 +11,7 @@ "alias": { "type": "string" } - }, - "required": [ - "alias" - ] + } } }, "required": [ diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 9670360cd..98a50b071 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -7339,6 +7339,10 @@ class AndroidInterface extends _InterfacePrototype.default { async isEnabled() { return (0, _autofillUtils.autofillEnabled)(this.globalConfig, _appleUtils.processConfig); } + /** + * @returns {Promise} + */ + async getAlias() { const { @@ -7639,20 +7643,9 @@ class AppleDeviceInterface extends _InterfacePrototype.default { if ('configType' in response) { this.selectedDetail(response.data, response.configType); } else if ('stop' in response) { - var _this$activeForm, _this$activeForm$acti; - - // Let input handlers know we've stopped autofilling - (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + await this.onFinishedAutofill(); } else if ('stateChange' in response) { - var _this$activeForm2, _this$activeForm3; - - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed - - await this.refreshData(); // Add correct icons and behaviour - - (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + await this.updateForStateChange(); } }); } @@ -7802,13 +7795,13 @@ class AppleDeviceInterface extends _InterfacePrototype.default { } getCurrentInputType() { - var _this$activeForm4; + var _this$activeForm; const topContextData = this.getTopContextData(); - return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm4 = this.activeForm) === null || _this$activeForm4 === void 0 ? void 0 : _this$activeForm4.activeInput); + return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : _this$activeForm.activeInput); } /** - * @returns {Promise} + * @returns {Promise} */ @@ -7817,9 +7810,10 @@ class AppleDeviceInterface extends _InterfacePrototype.default { alias } = await this.deviceApi.request(new _additionalDeviceApiCalls.GetAlias({ requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp + shouldConsumeAliasIfProvided: !this.globalConfig.isApp, + isIncontextSignupAvailable: this.inContextSignup.isAvailable() })); - return (0, _autofillUtils.formatDuckAddress)(alias); + return alias ? (0, _autofillUtils.formatDuckAddress)(alias) : alias; } addLogoutListener(handler) { @@ -8919,8 +8913,6 @@ class InterfacePrototype { if (this.globalConfig.isMobileApp && inputType === 'identities.emailAddress') { this.getAlias().then(alias => { - var _form$activeInput; - if (alias) { form.autofillEmail(alias); /** @@ -8929,7 +8921,16 @@ class InterfacePrototype { */ this.emailProtection.storeReceived(alias); - } else (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } else { + var _form$activeInput; + + (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } // Update data from native-side in case the `getAlias` call + // has included a successful in-context signup + + + this.updateForStateChange(); + this.onFinishedAutofill(); }); return; } @@ -9074,6 +9075,25 @@ class InterfacePrototype { return (_this$uiController4 = this.uiController) === null || _this$uiController4 === void 0 ? void 0 : (_this$uiController4$r = _this$uiController4.removeTooltip) === null || _this$uiController4$r === void 0 ? void 0 : _this$uiController4$r.call(_this$uiController4, 'interface'); } + onFinishedAutofill() { + var _this$activeForm, _this$activeForm$acti; + + // Let input handlers know we've stopped autofilling + (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + } + + async updateForStateChange() { + var _this$activeForm2, _this$activeForm3; + + // Remove decorations before refreshing data to make sure we + // remove the currently set icons + (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed + + await this.refreshData(); // Add correct icons and behaviour + + (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + } + async refreshData() { var _this$inContextSignup; @@ -9231,12 +9251,12 @@ class InterfacePrototype { return false; } /** - * @returns {Promise} + * @returns {Promise} */ async getAlias() { - return null; + return undefined; } // PM endpoints @@ -10571,7 +10591,7 @@ class Form { * Adds event listeners and keeps track of them for subsequent removal * @param {HTMLElement} el * @param {Event['type']} type - * @param {() => void} fn + * @param {(Event) => void} fn * @param {AddEventListenerOptions} [opts] */ @@ -10643,7 +10663,11 @@ class Form { }); } }); - } // We need click coordinates to position the tooltip when the field is in an iframe + } + /** + * @param {PointerEvent} e + * @returns {{ x: number; y: number; } | undefined} + */ function getMainClickCoords(e) { @@ -10654,22 +10678,54 @@ class Form { x: e.clientX, y: e.clientY }; + } + /** + * @param {Event} e + * @param {WeakMap} storedClickCoords + * @returns {{ x: number; y: number; } | null} + */ + + + function getClickCoords(e, storedClickCoords) { + // Get click co-ordinates for pointer events + // We need click coordinates to position the tooltip when the field is in an iframe + if (e.type === 'pointerdown') { + return getMainClickCoords( + /** @type {PointerEvent} */ + e) || null; + } // Reuse a previous click co-ordinates if they exist for this element + + + const click = storedClickCoords.get(input); + storedClickCoords.delete(input); + return click || null; } // Store the click to a label so we can use the click when the field is focused // Needed to handle label clicks when the form is in an iframe - let storedClick = new WeakMap(); + let storedClickCoords = new WeakMap(); let timeout = null; + /** + * @param {PointerEvent} e + */ const handlerLabel = e => { + var _e$target, _e$target$closest; + // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = e.target.closest('label').control; + const control = + /** @type HTMLElement */ + (_e$target = e.target) === null || _e$target === void 0 ? void 0 : (_e$target$closest = _e$target.closest('label')) === null || _e$target$closest === void 0 ? void 0 : _e$target$closest.control; if (!control) return; - storedClick.set(control, getMainClickCoords(e)); + + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)); + } + clearTimeout(timeout); // Remove the stored click if the timer expires timeout = setTimeout(() => { - storedClick = new WeakMap(); + storedClickCoords = new WeakMap(); }, 1000); }; @@ -10683,17 +10739,13 @@ class Form { const isLabel = e.target instanceof HTMLLabelElement; const input = isLabel ? e.target.control : e.target; if (!input || !this.inputs.all.has(input)) return; - let click = null; if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; // Checks for pointerdown event. Needed for positioning when fields are within an iframe + if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; + const clickCoords = getClickCoords(e, storedClickCoords); if (e.type === 'pointerdown') { - click = getMainClickCoords(e); - if (!click) return; - } else if (storedClick) { - // Reuse a previous click if one exists for this element - click = storedClick.get(input); - storedClick.delete(input); + // Only allow real user clicks with co-ordinates through + if (!e.isTrusted || !clickCoords) return; } if (this.shouldOpenTooltip(e, input)) { @@ -10711,7 +10763,7 @@ class Form { this.device.attachTooltip({ form: this, input: input, - click: click, + click: clickCoords, trigger: 'userInitiated', triggerMetaData: { // An 'icon' click is very different to a field click or focus. @@ -10768,7 +10820,7 @@ class Form { return true; } - if (this.device.globalConfig.isExtension) { + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { // Don't open the tooltip on input focus whenever it's showing in-context signup if (isIncontextSignupAvailable) return false; } @@ -14659,15 +14711,21 @@ class InContextSignup { await ((_this$device$uiContro = this.device.uiController) === null || _this$device$uiContro === void 0 ? void 0 : _this$device$uiContro.removeTooltip('stateChange')); // Make sure the input doesn't have focus so we can focus on it again const activeInput = (_this$device$activeFo = this.device.activeForm) === null || _this$device$activeFo === void 0 ? void 0 : _this$device$activeFo.activeInput; - activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Focus on the active input to open the tooltip + activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Select the active input to open the tooltip - const focusActiveInput = () => { + const selectActiveInput = () => { activeInput === null || activeInput === void 0 ? void 0 : activeInput.focus(); }; - document.hasFocus() ? focusActiveInput() : document.addEventListener('visibilitychange', focusActiveInput, { - once: true - }); + if (document.hasFocus()) { + selectActiveInput(); + } else { + document.addEventListener('visibilitychange', () => { + selectActiveInput(); + }, { + once: true + }); + } } isPermanentlyDismissed() { @@ -19097,14 +19155,15 @@ exports.autofillFeatureTogglesSchema = autofillFeatureTogglesSchema; const getAliasParamsSchema = _zod.z.object({ requiresUserPermission: _zod.z.boolean(), - shouldConsumeAliasIfProvided: _zod.z.boolean() + shouldConsumeAliasIfProvided: _zod.z.boolean(), + isIncontextSignupAvailable: _zod.z.boolean().optional() }); exports.getAliasParamsSchema = getAliasParamsSchema; const getAliasResultSchema = _zod.z.object({ success: _zod.z.object({ - alias: _zod.z.string() + alias: _zod.z.string().optional() }) }); diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 743940a67..951d6f80d 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -3663,6 +3663,10 @@ class AndroidInterface extends _InterfacePrototype.default { async isEnabled() { return (0, _autofillUtils.autofillEnabled)(this.globalConfig, _appleUtils.processConfig); } + /** + * @returns {Promise} + */ + async getAlias() { const { @@ -3963,20 +3967,9 @@ class AppleDeviceInterface extends _InterfacePrototype.default { if ('configType' in response) { this.selectedDetail(response.data, response.configType); } else if ('stop' in response) { - var _this$activeForm, _this$activeForm$acti; - - // Let input handlers know we've stopped autofilling - (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + await this.onFinishedAutofill(); } else if ('stateChange' in response) { - var _this$activeForm2, _this$activeForm3; - - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed - - await this.refreshData(); // Add correct icons and behaviour - - (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + await this.updateForStateChange(); } }); } @@ -4126,13 +4119,13 @@ class AppleDeviceInterface extends _InterfacePrototype.default { } getCurrentInputType() { - var _this$activeForm4; + var _this$activeForm; const topContextData = this.getTopContextData(); - return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm4 = this.activeForm) === null || _this$activeForm4 === void 0 ? void 0 : _this$activeForm4.activeInput); + return topContextData !== null && topContextData !== void 0 && topContextData.inputType ? topContextData.inputType : (0, _matching.getInputType)((_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : _this$activeForm.activeInput); } /** - * @returns {Promise} + * @returns {Promise} */ @@ -4141,9 +4134,10 @@ class AppleDeviceInterface extends _InterfacePrototype.default { alias } = await this.deviceApi.request(new _additionalDeviceApiCalls.GetAlias({ requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp + shouldConsumeAliasIfProvided: !this.globalConfig.isApp, + isIncontextSignupAvailable: this.inContextSignup.isAvailable() })); - return (0, _autofillUtils.formatDuckAddress)(alias); + return alias ? (0, _autofillUtils.formatDuckAddress)(alias) : alias; } addLogoutListener(handler) { @@ -5243,8 +5237,6 @@ class InterfacePrototype { if (this.globalConfig.isMobileApp && inputType === 'identities.emailAddress') { this.getAlias().then(alias => { - var _form$activeInput; - if (alias) { form.autofillEmail(alias); /** @@ -5253,7 +5245,16 @@ class InterfacePrototype { */ this.emailProtection.storeReceived(alias); - } else (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } else { + var _form$activeInput; + + (_form$activeInput = form.activeInput) === null || _form$activeInput === void 0 ? void 0 : _form$activeInput.focus(); + } // Update data from native-side in case the `getAlias` call + // has included a successful in-context signup + + + this.updateForStateChange(); + this.onFinishedAutofill(); }); return; } @@ -5398,6 +5399,25 @@ class InterfacePrototype { return (_this$uiController4 = this.uiController) === null || _this$uiController4 === void 0 ? void 0 : (_this$uiController4$r = _this$uiController4.removeTooltip) === null || _this$uiController4$r === void 0 ? void 0 : _this$uiController4$r.call(_this$uiController4, 'interface'); } + onFinishedAutofill() { + var _this$activeForm, _this$activeForm$acti; + + // Let input handlers know we've stopped autofilling + (_this$activeForm = this.activeForm) === null || _this$activeForm === void 0 ? void 0 : (_this$activeForm$acti = _this$activeForm.activeInput) === null || _this$activeForm$acti === void 0 ? void 0 : _this$activeForm$acti.dispatchEvent(new Event('mouseleave')); + } + + async updateForStateChange() { + var _this$activeForm2, _this$activeForm3; + + // Remove decorations before refreshing data to make sure we + // remove the currently set icons + (_this$activeForm2 = this.activeForm) === null || _this$activeForm2 === void 0 ? void 0 : _this$activeForm2.removeAllDecorations(); // Update for any state that may have changed + + await this.refreshData(); // Add correct icons and behaviour + + (_this$activeForm3 = this.activeForm) === null || _this$activeForm3 === void 0 ? void 0 : _this$activeForm3.recategorizeAllInputs(); + } + async refreshData() { var _this$inContextSignup; @@ -5555,12 +5575,12 @@ class InterfacePrototype { return false; } /** - * @returns {Promise} + * @returns {Promise} */ async getAlias() { - return null; + return undefined; } // PM endpoints @@ -6895,7 +6915,7 @@ class Form { * Adds event listeners and keeps track of them for subsequent removal * @param {HTMLElement} el * @param {Event['type']} type - * @param {() => void} fn + * @param {(Event) => void} fn * @param {AddEventListenerOptions} [opts] */ @@ -6967,7 +6987,11 @@ class Form { }); } }); - } // We need click coordinates to position the tooltip when the field is in an iframe + } + /** + * @param {PointerEvent} e + * @returns {{ x: number; y: number; } | undefined} + */ function getMainClickCoords(e) { @@ -6978,22 +7002,54 @@ class Form { x: e.clientX, y: e.clientY }; + } + /** + * @param {Event} e + * @param {WeakMap} storedClickCoords + * @returns {{ x: number; y: number; } | null} + */ + + + function getClickCoords(e, storedClickCoords) { + // Get click co-ordinates for pointer events + // We need click coordinates to position the tooltip when the field is in an iframe + if (e.type === 'pointerdown') { + return getMainClickCoords( + /** @type {PointerEvent} */ + e) || null; + } // Reuse a previous click co-ordinates if they exist for this element + + + const click = storedClickCoords.get(input); + storedClickCoords.delete(input); + return click || null; } // Store the click to a label so we can use the click when the field is focused // Needed to handle label clicks when the form is in an iframe - let storedClick = new WeakMap(); + let storedClickCoords = new WeakMap(); let timeout = null; + /** + * @param {PointerEvent} e + */ const handlerLabel = e => { + var _e$target, _e$target$closest; + // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = e.target.closest('label').control; + const control = + /** @type HTMLElement */ + (_e$target = e.target) === null || _e$target === void 0 ? void 0 : (_e$target$closest = _e$target.closest('label')) === null || _e$target$closest === void 0 ? void 0 : _e$target$closest.control; if (!control) return; - storedClick.set(control, getMainClickCoords(e)); + + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)); + } + clearTimeout(timeout); // Remove the stored click if the timer expires timeout = setTimeout(() => { - storedClick = new WeakMap(); + storedClickCoords = new WeakMap(); }, 1000); }; @@ -7007,17 +7063,13 @@ class Form { const isLabel = e.target instanceof HTMLLabelElement; const input = isLabel ? e.target.control : e.target; if (!input || !this.inputs.all.has(input)) return; - let click = null; if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; // Checks for pointerdown event. Needed for positioning when fields are within an iframe + if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; + const clickCoords = getClickCoords(e, storedClickCoords); if (e.type === 'pointerdown') { - click = getMainClickCoords(e); - if (!click) return; - } else if (storedClick) { - // Reuse a previous click if one exists for this element - click = storedClick.get(input); - storedClick.delete(input); + // Only allow real user clicks with co-ordinates through + if (!e.isTrusted || !clickCoords) return; } if (this.shouldOpenTooltip(e, input)) { @@ -7035,7 +7087,7 @@ class Form { this.device.attachTooltip({ form: this, input: input, - click: click, + click: clickCoords, trigger: 'userInitiated', triggerMetaData: { // An 'icon' click is very different to a field click or focus. @@ -7092,7 +7144,7 @@ class Form { return true; } - if (this.device.globalConfig.isExtension) { + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { // Don't open the tooltip on input focus whenever it's showing in-context signup if (isIncontextSignupAvailable) return false; } @@ -10983,15 +11035,21 @@ class InContextSignup { await ((_this$device$uiContro = this.device.uiController) === null || _this$device$uiContro === void 0 ? void 0 : _this$device$uiContro.removeTooltip('stateChange')); // Make sure the input doesn't have focus so we can focus on it again const activeInput = (_this$device$activeFo = this.device.activeForm) === null || _this$device$activeFo === void 0 ? void 0 : _this$device$activeFo.activeInput; - activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Focus on the active input to open the tooltip + activeInput === null || activeInput === void 0 ? void 0 : activeInput.blur(); // Select the active input to open the tooltip - const focusActiveInput = () => { + const selectActiveInput = () => { activeInput === null || activeInput === void 0 ? void 0 : activeInput.focus(); }; - document.hasFocus() ? focusActiveInput() : document.addEventListener('visibilitychange', focusActiveInput, { - once: true - }); + if (document.hasFocus()) { + selectActiveInput(); + } else { + document.addEventListener('visibilitychange', () => { + selectActiveInput(); + }, { + once: true + }); + } } isPermanentlyDismissed() {