Skip to content

Commit

Permalink
CopyHandler: Fix unintended Copy override when copying from input or …
Browse files Browse the repository at this point in the history
…textarea elements (#22793)

* CopyHandler: Fix unintended Copy override when copying from input/textarea elements

Affects blocks such as Embed, Code, Custom HTML, Shortcode.

Fixes #22775

* E2E: Extend "copy whole blocks" suite to cover Shortcode block
  • Loading branch information
mcsf authored Jun 4, 2020
1 parent ea9c463 commit a34d51b
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 11 deletions.
7 changes: 5 additions & 2 deletions packages/block-editor/src/components/copy-handler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
*/
import { useCallback, useRef } from '@wordpress/element';
import { serialize, pasteHandler } from '@wordpress/blocks';
import { documentHasSelection, documentHasTextSelection } from '@wordpress/dom';
import {
documentHasSelection,
documentHasUncollapsedSelection,
} from '@wordpress/dom';
import { useDispatch, useSelect } from '@wordpress/data';
import { __, sprintf } from '@wordpress/i18n';

Expand Down Expand Up @@ -93,7 +96,7 @@ function CopyHandler( { children } ) {
// Otherwise, any focus on an input field is considered.
const hasSelection =
event.type === 'copy' || event.type === 'cut'
? documentHasTextSelection()
? documentHasUncollapsedSelection()
: documentHasSelection();

// Let native copy behaviour take over in input fields.
Expand Down
4 changes: 4 additions & 0 deletions packages/dom/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Feature

- Add `documentHasUncollapsedSelection` to inquire about ranges of selected text in the document, including the separately managed selections inside <input> and <textarea> elements.

## 2.10.0 (2020-05-28)

### New Feature
Expand Down
16 changes: 15 additions & 1 deletion packages/dom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,26 @@ _Returns_

<a name="documentHasTextSelection" href="#documentHasTextSelection">#</a> **documentHasTextSelection**

Check whether the current document has selected text.
Check whether the current document has selected text. This applies to ranges
of text in the document, and not selection inside <input> and <textarea>
elements.

See: <https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects>.

_Returns_

- `boolean`: True if there is selection, false if not.

<a name="documentHasUncollapsedSelection" href="#documentHasUncollapsedSelection">#</a> **documentHasUncollapsedSelection**

Check whether the current document has any sort of selection. This includes
ranges of text across elements and any selection inside <input> and
<textarea> elements.
_Returns_
- `boolean`: Whether there is any sort of "selection" in the document.
<a name="focus" href="#focus">#</a> **focus**
Object grouping `focusable` and `tabbable` utils
Expand Down
54 changes: 53 additions & 1 deletion packages/dom/src/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,11 @@ export function isNumberInput( element ) {
}

/**
* Check whether the current document has selected text.
* Check whether the current document has selected text. This applies to ranges
* of text in the document, and not selection inside <input> and <textarea>
* elements.
*
* See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects.
*
* @return {boolean} True if there is selection, false if not.
*/
Expand All @@ -494,6 +498,54 @@ export function documentHasTextSelection() {
return range && ! range.collapsed;
}

/**
* Check whether the given element, assumed an input field or textarea,
* contains a (uncollapsed) selection of text.
*
* Note: this is perhaps an abuse of the term "selection", since these elements
* manage selection differently and aren't covered by Selection#collapsed.
*
* See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects.
*
* @param {HTMLElement} element The HTML element.
*
* @return {boolean} Whether the input/textareaa element has some "selection".
*/
function inputFieldHasUncollapsedSelection( element ) {
if ( ! isTextField( element ) && ! isNumberInput( element ) ) {
return false;
}
try {
const { selectionStart, selectionEnd } = element;

return selectionStart !== null && selectionStart !== selectionEnd;
} catch ( error ) {
// Safari throws an exception when trying to get `selectionStart`
// on non-text <input> elements (which, understandably, don't
// have the text selection API). We catch this via a try/catch
// block, as opposed to a more explicit check of the element's
// input types, because of Safari's non-standard behavior. This
// also means we don't have to worry about the list of input
// types that support `selectionStart` changing as the HTML spec
// evolves over time.
return false;
}
}

/**
* Check whether the current document has any sort of selection. This includes
* ranges of text across elements and any selection inside <input> and
* <textarea> elements.
*
* @return {boolean} Whether there is any sort of "selection" in the document.
*/
export function documentHasUncollapsedSelection() {
return (
documentHasTextSelection() ||
inputFieldHasUncollapsedSelection( document.activeElement )
);
}

/**
* Check whether the current document has a selection. This checks for both
* focus in an input field and general text selection.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Multi-block selection should copy and paste individual blocks 1`] = `
exports[`Copy/cut/paste of whole blocks should copy and paste individual blocks 1`] = `
"<!-- wp:paragraph -->
<p>Here is a unique string so we can test copying.</p>
<!-- /wp:paragraph -->
Expand All @@ -10,7 +10,7 @@ exports[`Multi-block selection should copy and paste individual blocks 1`] = `
<!-- /wp:paragraph -->"
`;

exports[`Multi-block selection should copy and paste individual blocks 2`] = `
exports[`Copy/cut/paste of whole blocks should copy and paste individual blocks 2`] = `
"<!-- wp:paragraph -->
<p>Here is a unique string so we can test copying.</p>
<!-- /wp:paragraph -->
Expand All @@ -24,13 +24,13 @@ exports[`Multi-block selection should copy and paste individual blocks 2`] = `
<!-- /wp:paragraph -->"
`;

exports[`Multi-block selection should cut and paste individual blocks 1`] = `
exports[`Copy/cut/paste of whole blocks should cut and paste individual blocks 1`] = `
"<!-- wp:paragraph -->
<p>2</p>
<!-- /wp:paragraph -->"
`;

exports[`Multi-block selection should cut and paste individual blocks 2`] = `
exports[`Copy/cut/paste of whole blocks should cut and paste individual blocks 2`] = `
"<!-- wp:paragraph -->
<p>2</p>
<!-- /wp:paragraph -->
Expand All @@ -40,7 +40,23 @@ exports[`Multi-block selection should cut and paste individual blocks 2`] = `
<!-- /wp:paragraph -->"
`;

exports[`Multi-block selection should respect inline copy when text is selected 1`] = `
exports[`Copy/cut/paste of whole blocks should respect inline copy in places like input fields and textareas 1`] = `
"<!-- wp:shortcode -->
[my-shortcode]
<!-- /wp:shortcode -->"
`;

exports[`Copy/cut/paste of whole blocks should respect inline copy in places like input fields and textareas 2`] = `
"<!-- wp:shortcode -->
[my-shortcode]
<!-- /wp:shortcode -->
<!-- wp:paragraph -->
<p>Pasted: e]</p>
<!-- /wp:paragraph -->"
`;

exports[`Copy/cut/paste of whole blocks should respect inline copy when text is selected 1`] = `
"<!-- wp:paragraph -->
<p>First block</p>
<!-- /wp:paragraph -->
Expand All @@ -50,7 +66,7 @@ exports[`Multi-block selection should respect inline copy when text is selected
<!-- /wp:paragraph -->"
`;

exports[`Multi-block selection should respect inline copy when text is selected 2`] = `
exports[`Copy/cut/paste of whole blocks should respect inline copy when text is selected 2`] = `
"<!-- wp:paragraph -->
<p>First block</p>
<!-- /wp:paragraph -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {
createNewPost,
pressKeyWithModifier,
getEditedPostContent,
insertBlock,
} from '@wordpress/e2e-test-utils';

describe( 'Multi-block selection', () => {
describe( 'Copy/cut/paste of whole blocks', () => {
beforeEach( async () => {
await createNewPost();
} );
Expand Down Expand Up @@ -63,4 +64,21 @@ describe( 'Multi-block selection', () => {
await pressKeyWithModifier( 'primary', 'v' );
expect( await getEditedPostContent() ).toMatchSnapshot();
} );

it( 'should respect inline copy in places like input fields and textareas', async () => {
await insertBlock( 'Shortcode' );
await page.keyboard.type( '[my-shortcode]' );
await pressKeyWithModifier( 'shift', 'ArrowLeft' );
await pressKeyWithModifier( 'shift', 'ArrowLeft' );

await pressKeyWithModifier( 'primary', 'c' );
await page.keyboard.press( 'ArrowRight' );
await page.keyboard.press( 'ArrowRight' );
expect( await getEditedPostContent() ).toMatchSnapshot();

await insertBlock( 'Paragraph' );
await page.keyboard.type( 'Pasted: ' );
await pressKeyWithModifier( 'primary', 'v' );
expect( await getEditedPostContent() ).toMatchSnapshot();
} );
} );

0 comments on commit a34d51b

Please sign in to comment.