From 47d1155219fb60948fc36f937928f05f4e9889b8 Mon Sep 17 00:00:00 2001 From: Tyler Biethman Date: Tue, 23 Aug 2022 21:28:23 -0500 Subject: [PATCH] Simplifying workaround for webkit focus selection --- .../commands/actions/type_special_chars.cy.js | 9 ++-- .../driver/src/cy/commands/actions/focus.ts | 13 ----- packages/driver/src/cy/focused.ts | 48 +++++-------------- 3 files changed, 15 insertions(+), 55 deletions(-) diff --git a/packages/driver/cypress/e2e/commands/actions/type_special_chars.cy.js b/packages/driver/cypress/e2e/commands/actions/type_special_chars.cy.js index 405857a55713..5e522e146bac 100644 --- a/packages/driver/cypress/e2e/commands/actions/type_special_chars.cy.js +++ b/packages/driver/cypress/e2e/commands/actions/type_special_chars.cy.js @@ -314,8 +314,8 @@ describe('src/cy/commands/actions/type - #type special chars', () => { attachKeyListeners({ input }) cy.get(':text:first').invoke('val', 'ab') - .focus() .then(($input) => $input[0].setSelectionRange(0, 0)) + .focus() .type('{backspace}') .should('have.value', 'ab') @@ -357,8 +357,8 @@ describe('src/cy/commands/actions/type - #type special chars', () => { attachKeyListeners({ input }) cy.get('textarea:first').invoke('val', 'ab') - .focus() .then(($textarea) => $textarea[0].setSelectionRange(0, 0)) + .focus() .type('{backspace}') .should('have.value', 'ab') @@ -452,8 +452,8 @@ describe('src/cy/commands/actions/type - #type special chars', () => { attachKeyListeners({ input }) cy.get(':text:first').invoke('val', 'ab') - .focus() .then(($input) => $input[0].setSelectionRange(0, 0)) + .focus() .type('{del}') .should('have.value', 'b') @@ -467,7 +467,6 @@ describe('src/cy/commands/actions/type - #type special chars', () => { attachKeyListeners({ input }) cy.get(':text:first').invoke('val', 'ab') - .then(($input) => $input[0].setSelectionRange(0, 0)) .focus() .type('{selectall}{del}') @@ -497,8 +496,8 @@ describe('src/cy/commands/actions/type - #type special chars', () => { attachKeyListeners({ textarea }) cy.get('textarea:first').invoke('val', 'ab') - .focus() .then(($textarea) => $textarea[0].setSelectionRange(0, 0)) + .focus() .type('{del}') .should('have.value', 'b') diff --git a/packages/driver/src/cy/commands/actions/focus.ts b/packages/driver/src/cy/commands/actions/focus.ts index 414c336c9891..e866def4c133 100644 --- a/packages/driver/src/cy/commands/actions/focus.ts +++ b/packages/driver/src/cy/commands/actions/focus.ts @@ -4,7 +4,6 @@ import $dom from '../../../dom' import $utils from '../../../cypress/utils' import $errUtils from '../../../cypress/error_utils' import $elements from '../../../dom/elements' -import $selection from '../../../dom/selection' import type { Log } from '../../../cypress/log' interface InternalFocusOptions extends Partial { @@ -90,18 +89,6 @@ export default (Commands, Cypress, cy) => { cy.fireFocus(el) - if (Cypress.isBrowser('webkit') && ( - $elements.isInput(el) || $elements.isTextarea(el) - )) { - // Force selection to end in WebKit, unless selection - // has been set by user. - // It's a curried function, so the 2 arguments are valid. - // @ts-ignore - $selection.moveSelectionToEnd(el, { - onlyIfEmptySelection: true, - }) - } - const verifyAssertions = () => { return cy.verifyUpcomingAssertions(options.$el, options, { onRetry: verifyAssertions, diff --git a/packages/driver/src/cy/focused.ts b/packages/driver/src/cy/focused.ts index 730cac177c6a..2a685acd46a1 100644 --- a/packages/driver/src/cy/focused.ts +++ b/packages/driver/src/cy/focused.ts @@ -123,17 +123,12 @@ export const create = (state: StateFunc) => ({ const $focused = this.getFocused(el.ownerDocument) let hasFocused = false - let onFocusCapture // we need to bind to the focus event here // because some browsers will not ever fire // the focus event if the window itself is not // currently focused const cleanup = () => { - if (onFocusCapture) { - $elements.callNativeMethod(el, 'removeEventListener', 'focus', onFocusCapture, { capture: true }) - } - return $elements.callNativeMethod(el, 'removeEventListener', 'focus', onFocus) } @@ -142,45 +137,24 @@ export const create = (state: StateFunc) => ({ } if (Cypress.isBrowser('webkit')) { - // By default, WebKit will select the contents of an input element when the input is focused. + // By default, WebKit will select the entire contents of an input element when the input is focused. // This is problematic, as we use the existence of any selection to determine whether // we adjust the input's cursor and prepare the input for receiving additional content. // Without intervention, we will always interpret this default selection as a user-performed selection // and persist it, leaving the selection contents to be overwritten rather than appended to // on subsequent actions. // - // In order to avoid this behavior, we use a focus event during the capture phase to set - // our own initial blank selection. This short-circuits WebKit's default behavior and ensures - // that any user-performed selections performed during the focus event's bubble phase are still applied. - - onFocusCapture = (event: FocusEvent) => { - const eventTarget = event.currentTarget as HTMLInputElement - - if (!eventTarget.setSelectionRange) { - return - } - - try { - // Prior to being focused, the element's selectionStart/End will be at 0. - // Even so, we need to explicitly call setSelectionRange here to prevent WebKit - // from selecting the contents after being focused. - // - // By re-setting the selection at the current start/end values, - // we ensure that any selection values set by previous event handlers - // are persisted. - eventTarget.setSelectionRange( - eventTarget.selectionStart, - eventTarget.selectionEnd, - ) - } catch (e) { - // Some input types do not support selections and will throw when - // setSelectionRange is called. We can ignore these errors, - // as these elements wouldn't have a selection to we need to - // prevent anyway. - } + // In order to avoid this behavior, we explicitly set the selection range with the current + // selectionStart/selectionEnd values. This will bypass the default select-all behavior, even though + // logically it looks like a no-op. + try { + el.setSelectionRange(el.selectionStart, el.selectionEnd) + } catch (e) { + // Some input types do not support selections and will throw when + // setSelectionRange is called. We can ignore these errors, + // as these elements wouldn't have a selection to we need to + // prevent anyway. } - - $elements.callNativeMethod(el, 'addEventListener', 'focus', onFocusCapture, { capture: true }) } $elements.callNativeMethod(el, 'addEventListener', 'focus', onFocus)