diff --git a/addons/addon-clipboard/package.json b/addons/addon-clipboard/package.json index 176fa861a..eb207679d 100644 --- a/addons/addon-clipboard/package.json +++ b/addons/addon-clipboard/package.json @@ -21,8 +21,5 @@ "package": "../../node_modules/.bin/webpack", "prepublishOnly": "npm run package", "start": "node ../../demo/start" - }, - "dependencies": { - "js-base64": "^3.7.5" } } diff --git a/addons/addon-clipboard/src/ClipboardAddon.ts b/addons/addon-clipboard/src/ClipboardAddon.ts index 58425924f..5e194880c 100644 --- a/addons/addon-clipboard/src/ClipboardAddon.ts +++ b/addons/addon-clipboard/src/ClipboardAddon.ts @@ -4,8 +4,7 @@ */ import type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm'; -import { type IClipboardProvider, ClipboardSelectionType, type IBase64 } from '@xterm/addon-clipboard'; -import { Base64 as JSBase64 } from 'js-base64'; +import { type IClipboardProvider, type IBase64 } from '@xterm/addon-clipboard'; export class ClipboardAddon implements ITerminalAddon { private _terminal?: Terminal; @@ -25,7 +24,7 @@ export class ClipboardAddon implements ITerminalAddon { return this._disposable?.dispose(); } - private _readText(sel: ClipboardSelectionType, data: string): void { + private _readText(sel: string, data: string): void { const b64 = this._base64.encodeText(data); this._terminal?.input(`\x1b]52;${sel};${b64}\x07`, false); } @@ -36,7 +35,7 @@ export class ClipboardAddon implements ITerminalAddon { return true; } - const pc = args[0] as ClipboardSelectionType; + const pc = args[0]; const pd = args[1]; if (pd === '?') { const text = this._provider.readText(pc); @@ -70,30 +69,23 @@ export class ClipboardAddon implements ITerminalAddon { } export class BrowserClipboardProvider implements IClipboardProvider { - public async readText(selection: ClipboardSelectionType): Promise { - if (selection !== 'c') { - return Promise.resolve(''); - } + public readText(selection: string): Promise { return navigator.clipboard.readText(); } - public async writeText(selection: ClipboardSelectionType, text: string): Promise { - if (selection !== 'c') { - return Promise.resolve(); - } + public writeText(selection: string, text: string): Promise { return navigator.clipboard.writeText(text); } } export class Base64 implements IBase64 { public encodeText(data: string): string { - return JSBase64.encode(data); + return btoa(data); } public decodeText(data: string): string { - const text = JSBase64.decode(data); - if (!JSBase64.isValid(data) || JSBase64.encode(text) !== data) { - return ''; - } - return text; + try { + return atob(data); + } catch {} + return ''; } } diff --git a/addons/addon-clipboard/test/ClipboardAddon.test.ts b/addons/addon-clipboard/test/ClipboardAddon.test.ts index d4d86d46b..2c04e619b 100644 --- a/addons/addon-clipboard/test/ClipboardAddon.test.ts +++ b/addons/addon-clipboard/test/ClipboardAddon.test.ts @@ -20,7 +20,7 @@ test.describe('ClipboardAddon', () => { test.beforeEach(async ({}, testInfo) => { // DEBT: This test doesn't work since the migration to @playwright/test - if (ctx.browser.browserType().name() !== 'chromium') { + if (ctx.browser.browserType().name() === 'webkit') { testInfo.skip(); return; } @@ -48,6 +48,14 @@ test.describe('ClipboardAddon', () => { await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`); deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); }); + test('primary selection', async () => { + await ctx.proxy.write(`\x1b]52;p;${testDataEncoded}\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); + }); + test('empty selection (default)', async () => { + await ctx.proxy.write(`\x1b]52;;${testDataEncoded}\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); + }); test('invalid base64 string', async () => { await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}invalid\x07`); deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), ''); @@ -60,15 +68,24 @@ test.describe('ClipboardAddon', () => { }); test.describe('read data', async function (): Promise { - test('simple string', async () => { + test.beforeAll(async () => { await ctx.page.evaluate(` window.data = []; window.term.onData(e => data.push(e)); `); + }); + test('simple string', async () => { + await ctx.page.evaluate('window.data.length = 0;'); await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world')); await ctx.proxy.write(`\x1b]52;c;?\x07`); deepEqual(await ctx.page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]); }); + test('primary selection', async () => { + await ctx.page.evaluate('window.data.length = 0;'); + await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world')); + await ctx.proxy.write(`\x1b]52;p;?\x07`); + deepEqual(await ctx.page.evaluate('window.data'), [`\x1b]52;p;${testDataEncoded}\x07`]); + }); test('clear clipboard', async () => { await ctx.proxy.write(`\x1b]52;c;!\x07`); await ctx.proxy.write(`\x1b]52;c;?\x07`); diff --git a/addons/addon-clipboard/typings/addon-clipboard.d.ts b/addons/addon-clipboard/typings/addon-clipboard.d.ts index 4ef12e0f7..f8e024ac3 100644 --- a/addons/addon-clipboard/typings/addon-clipboard.d.ts +++ b/addons/addon-clipboard/typings/addon-clipboard.d.ts @@ -30,18 +30,6 @@ declare module '@xterm/addon-clipboard' { public dispose(): void } - /** - * Clipboard selection type. This is used to specify which selection buffer to - * read or write to. - * - SYSTEM `c`: The system clipboard. - * - PRIMARY `p`: The primary clipboard. This is provided for compatibility - * with Linux X11. - */ - export const enum ClipboardSelectionType { - SYSTEM = 'c', - PRIMARY = 'p', - } - export interface IBase64 { /** * Converts a utf-8 string to a base64 string. @@ -78,36 +66,39 @@ declare module '@xterm/addon-clipboard' { export interface IClipboardProvider { /** * Gets the clipboard content. - * @param selection The clipboard selection to read. + * @param selection The clipboard selection to read (see OSC 52 spec of xterm). * @returns A promise that resolves with clipboard selection data. */ - readText(selection: ClipboardSelectionType): string | Promise; + readText(selection: string): string | Promise; /** * Sets the clipboard content. - * @param selection The clipboard selection to set. + * @param selection The clipboard selection to set (see OSC 52 spec of xterm). * @param data The clipboard text to write. */ - writeText(selection: ClipboardSelectionType, text: string): void | Promise; + writeText(selection: string, text: string): void | Promise; } /** - * The clipboard provider interface that enables xterm.js to access the system clipboard. + * The clipboard provider interface that enables xterm.js to access the browser clipboard. + * + * Note this this clipboard provider does not implement own cut buffers as xterm does, + * but redirect the selection parameter always to navigator.clipboard. */ export class BrowserClipboardProvider implements IClipboardProvider{ /** * Reads text from the clipboard. - * @param selection The selection type to read from. + * @param selection The selection type to read from (always refers to navigator.clipboard). * @returns A promise that resolves with the text from the clipboard. */ - public readText(selection: ClipboardSelectionType): Promise; + public readText(selection: string): Promise; /** * Writes text to the clipboard. - * @param selection The selection type to write to. + * @param selection The selection type to write to (always refers to navigator.clipboard). * @param data The text to write to the clipboard. * @returns A promise that resolves when the text has been written to the clipboard. */ - public writeText(selection: ClipboardSelectionType, data: string): Promise; + public writeText(selection: string, data: string): Promise; } } diff --git a/package-lock.json b/package-lock.json index ebe3acbef..23f9bbc2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,10 +64,7 @@ "addons/addon-clipboard": { "name": "@xterm/addon-clipboard", "version": "0.2.0", - "license": "MIT", - "dependencies": { - "js-base64": "^3.7.5" - } + "license": "MIT" }, "addons/addon-fit": { "name": "@xterm/addon-fit", @@ -5420,11 +5417,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-base64": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",