Prevent Firefox's global paste behavior if paste target is not also focused (#2581)
* Prevent Firefox's global paste behavior if paste target is not also focused * Write a test, fix code thanks to test * Add one more comment to test
This commit is contained in:
@ -5,6 +5,7 @@ import {
|
||||
getMovementUtils,
|
||||
wiggleMove,
|
||||
doExport,
|
||||
metaModifier,
|
||||
} from './test-utils'
|
||||
import waitOn from 'wait-on'
|
||||
import { XOR, roundOff, uuidv4 } from 'lib/utils'
|
||||
@ -4846,3 +4847,51 @@ const part001 = startSketchOn('-XZ')
|
||||
// file types in snapshot-tests.spec.ts
|
||||
await expect(page.getByText('Exported successfully')).toBeVisible()
|
||||
})
|
||||
|
||||
test('Paste should not work unless an input is focused', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
// To run this test locally, uncomment Firefox in playwright.config.ts
|
||||
test.skip(
|
||||
browserName !== 'firefox',
|
||||
"This bug is really Firefox-only, which we don't run in CI."
|
||||
)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
|
||||
const codeEditorText = page.locator('.cm-content')
|
||||
const pasteContent = `// was this pasted?`
|
||||
const typeContent = `// this should be typed`
|
||||
|
||||
// Load text into the clipboard
|
||||
await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent)
|
||||
|
||||
// Focus the text editor
|
||||
await codeEditorText.focus()
|
||||
|
||||
// Show that we can type into it
|
||||
await page.keyboard.type(typeContent)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Paste without the code pane focused
|
||||
await codeEditorText.blur()
|
||||
await page.keyboard.press(`${metaModifier}+KeyV`)
|
||||
|
||||
// Show that the paste didn't work but typing did
|
||||
await expect(codeEditorText).not.toContainText(pasteContent)
|
||||
await expect(codeEditorText).toContainText(typeContent)
|
||||
|
||||
// Paste with the code editor focused
|
||||
// Following this guidance: https://github.com/microsoft/playwright/issues/8114
|
||||
await codeEditorText.focus()
|
||||
await page.keyboard.press(`${metaModifier}+KeyV`)
|
||||
await expect(
|
||||
await page.evaluate(
|
||||
() => document.querySelector('.cm-content')?.textContent
|
||||
)
|
||||
).toContain(pasteContent)
|
||||
})
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { test, expect, Page, Download } from '@playwright/test'
|
||||
import { EngineCommand } from '../../src/lang/std/engineConnection'
|
||||
import os from 'os'
|
||||
import fsp from 'fs/promises'
|
||||
import pixelMatch from 'pixelmatch'
|
||||
import { PNG } from 'pngjs'
|
||||
@ -443,3 +444,8 @@ export const doExport = async (
|
||||
outputType: output.type,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate modifier key for the platform.
|
||||
*/
|
||||
export const metaModifier = os.platform() === 'darwin' ? 'Meta' : 'Control'
|
||||
|
@ -34,7 +34,14 @@ export default defineConfig({
|
||||
projects: [
|
||||
{
|
||||
name: 'Google Chrome',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chrome' }, // or 'chrome-beta'
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
channel: 'chrome',
|
||||
contextOptions: {
|
||||
/* Chromium is the only one with these permission types */
|
||||
permissions: ['clipboard-write', 'clipboard-read'],
|
||||
},
|
||||
}, // or 'chrome-beta'
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
|
@ -35,6 +35,38 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
overallState === NetworkHealthState.Ok ||
|
||||
overallState === NetworkHealthState.Weak
|
||||
|
||||
// Linux has a default behavior to paste text on middle mouse up
|
||||
// This adds a listener to block that pasting if the click target
|
||||
// is not a text input, so users can move in the 3D scene with
|
||||
// middle mouse drag with a text input focused without pasting.
|
||||
useEffect(() => {
|
||||
const handlePaste = (e: ClipboardEvent) => {
|
||||
const isHtmlElement = e.target && e.target instanceof HTMLElement
|
||||
const isEditable =
|
||||
(isHtmlElement && !('explicitOriginalTarget' in e)) ||
|
||||
('explicitOriginalTarget' in e &&
|
||||
((e.explicitOriginalTarget as HTMLElement).contentEditable ===
|
||||
'true' ||
|
||||
['INPUT', 'TEXTAREA'].some(
|
||||
(tagName) =>
|
||||
tagName === (e.explicitOriginalTarget as HTMLElement).tagName
|
||||
)))
|
||||
if (!isEditable) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
}
|
||||
}
|
||||
|
||||
globalThis?.window?.document?.addEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
return () =>
|
||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof window === 'undefined' ||
|
||||
|
Reference in New Issue
Block a user