diff --git a/addons/addon-clipboard/src/ClipboardAddon.ts b/addons/addon-clipboard/src/ClipboardAddon.ts index 5e194880c..535c450be 100644 --- a/addons/addon-clipboard/src/ClipboardAddon.ts +++ b/addons/addon-clipboard/src/ClipboardAddon.ts @@ -78,13 +78,43 @@ export class BrowserClipboardProvider implements IClipboardProvider { } } +/** + * TODO: Once the base64 handling on Uint8Arrays is more common, + * remove the btoa/atob fallbacks below. + */ +interface IUint8ArrayB64 extends Uint8Array { + toBase64(): string; +} +interface IUint8ArrayB64Ctor extends Uint8ArrayConstructor { + fromBase64(s: string): IUint8ArrayB64; +} + export class Base64 implements IBase64 { public encodeText(data: string): string { - return btoa(data); + const bytes = new TextEncoder().encode(data) as IUint8ArrayB64; + if (bytes.toBase64 !== undefined) { + return bytes.toBase64(); + } + let bin = ''; + for (let i = 0; i < bytes.length; i++) { + bin += String.fromCharCode(bytes[i]); + } + return btoa(bin); } public decodeText(data: string): string { + if ((Uint8Array as IUint8ArrayB64Ctor).fromBase64 !== undefined) { + try { + return new TextDecoder().decode((Uint8Array as IUint8ArrayB64Ctor).fromBase64(data)); + } catch {} + return ''; + } try { - return atob(data); + const bin = atob(data); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + bytes[i] = bin.charCodeAt(i); + } + return new TextDecoder().decode(bytes); } catch {} return ''; } diff --git a/addons/addon-clipboard/test/ClipboardAddon.test.ts b/addons/addon-clipboard/test/ClipboardAddon.test.ts index 2c04e619b..6995850d4 100644 --- a/addons/addon-clipboard/test/ClipboardAddon.test.ts +++ b/addons/addon-clipboard/test/ClipboardAddon.test.ts @@ -29,7 +29,7 @@ test.describe('ClipboardAddon', () => { await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); } await ctx.page.evaluate(` - window.term.reset() + window.term.reset(); window.clipboard?.dispose(); window.clipboard = new ClipboardAddon(); window.term.loadAddon(window.clipboard); @@ -92,4 +92,22 @@ test.describe('ClipboardAddon', () => { deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), ''); }); }); + + test.describe('non-ASCII', () => { + const testDataEncoded = '4oKsbWzDpMO8dMOf'; + const testDataDecoded = '€mläütß'; + test('write simple string', async () => { + await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); + }); + test('read simple string', async () => { + await ctx.page.evaluate(` + window.data2 = []; + window.term.onData(e => data2.push(e)); + `); + await ctx.page.evaluate((d) => window.navigator.clipboard.writeText(d), testDataDecoded); + await ctx.proxy.write(`\x1b]52;c;?\x07`); + deepEqual(await ctx.page.evaluate('window.data2'), [`\x1b]52;c;${testDataEncoded}\x07`]); + }); + }); });