Add support to ANSI OSC52 sequence to manipulate selection and clipboard data. The sequence specs supports multiple clipboard selections but we only support the common ones, system and primary clipboard selections. This adds a new event listener to the common terminal module `onClipboard` to allow external implementations to hook into it. The addon uses the browser Clipboard API to read/write from and to the clipboard. The default `ClipboardProvider` uses the browser Clipboard API. This means it only supports read/write to and from the system clipboard. Reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands Fixes: xtermjs#3260 Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
/**
|
|
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { assert } from 'chai';
|
|
import { openTerminal, launchBrowser, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
|
import { Browser, BrowserContext, Page } from '@playwright/test';
|
|
|
|
const APP = 'http://127.0.0.1:3001/test';
|
|
|
|
let browser: Browser;
|
|
let context: BrowserContext;
|
|
let page: Page;
|
|
const width = 800;
|
|
const height = 600;
|
|
|
|
describe('ClipboardAddon', () => {
|
|
before(async function (): Promise<any> {
|
|
browser = await launchBrowser({
|
|
// Enable clipboard access in firefox, mainly for readText
|
|
firefoxUserPrefs: {
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
'dom.events.testing.asyncClipboard': true,
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
'dom.events.asyncClipboard.readText': true
|
|
}
|
|
});
|
|
context = await browser.newContext();
|
|
if (getBrowserType().name() !== 'webkit') {
|
|
// Enable clipboard access in chromium without user gesture
|
|
context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
|
}
|
|
page = await context.newPage();
|
|
await page.setViewportSize({ width, height });
|
|
await page.goto(APP);
|
|
await openTerminal(page, { allowClipboardAccess: true });
|
|
await page.evaluate(`
|
|
window.clipboardAddon = new ClipboardAddon();
|
|
window.term.loadAddon(window.clipboardAddon);
|
|
`);
|
|
});
|
|
|
|
after(() => {
|
|
browser.close();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await page.evaluate(`window.term.reset()`);
|
|
});
|
|
|
|
const testDataEncoded = 'aGVsbG8gd29ybGQ=';
|
|
const testDataDecoded = 'hello world';
|
|
|
|
describe('write data', async function (): Promise<any> {
|
|
it('simple string', async () => {
|
|
await writeSync(page, `\x1b]52;c;${testDataEncoded}\x07`);
|
|
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded);
|
|
});
|
|
it('invalid base64 string', async () => {
|
|
await writeSync(page, `\x1b]52;c;${testDataEncoded}invalid\x07`);
|
|
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), '');
|
|
});
|
|
it('empty string', async () => {
|
|
await writeSync(page, `\x1b]52;c;\x07`);
|
|
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), '');
|
|
});
|
|
});
|
|
|
|
describe('read data', async function (): Promise<any> {
|
|
it('simple string', async () => {
|
|
await page.evaluate(`
|
|
window.data = [];
|
|
window.term.onData(e => data.push(e));
|
|
`);
|
|
await page.evaluate(() => window.navigator.clipboard.writeText('hello world'));
|
|
await writeSync(page, `\x1b]52;c;?\x07`);
|
|
assert.deepEqual(await page.evaluate(`window.data`), [testDataEncoded]);
|
|
});
|
|
it('clear clipboard', async () => {
|
|
await writeSync(page, `\x1b]52;c;!\x07`);
|
|
await writeSync(page, `\x1b]52;c;?\x07`);
|
|
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), '');
|
|
});
|
|
});
|
|
});
|