From 22f1ce4a115795f2ef12f49e0013eece2b633122 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:16:48 -0800 Subject: [PATCH 1/8] Add bg+powerline background blend behavior When a with bg is selected, the cell's regular background will be blended with 50% opacity selectionBackground in order to retain the bg color info as well as getting the color to change and getting decent contrast. This commit also fixes a terrible, nasty bug where the standalone shared renderer tests were causing the terminal to open multiple times and not get the WebGL addon activated the second time. So some tests were actually testing the DOM renderer :o Fixes #4918 --- .../renderer/dom/DomRendererRowFactory.ts | 4 +- .../renderer/shared/CellColorResolver.ts | 102 ++++++++++++++++-- src/browser/renderer/shared/RendererUtils.ts | 2 +- src/browser/renderer/shared/TextureAtlas.ts | 4 +- src/common/Color.test.ts | 21 ++++ src/common/Color.ts | 17 +++ test/playwright/Renderer.test.ts | 5 +- test/playwright/SharedRendererTests.ts | 93 +++++++++++----- 8 files changed, 208 insertions(+), 40 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 6ab68e7d..50d3eb49 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -11,7 +11,7 @@ import { ICoreService, IDecorationService, IOptionsService } from 'common/servic import { color, rgba } from 'common/Color'; import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; -import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; +import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils'; import { AttributeData } from 'common/buffer/AttributeData'; import { WidthCache } from 'browser/renderer/dom/WidthCache'; import { IColorContrastCache } from 'browser/Types'; @@ -458,7 +458,7 @@ export class DomRendererRowFactory { } private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean { - if (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode())) { + if (this._optionsService.rawOptions.minimumContrastRatio === 1 || treatGlyphAsBackgroundColor(cell.getCode())) { return false; } diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 5837a675..50725108 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -5,6 +5,8 @@ import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle import { IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from '@xterm/xterm'; +import { rgba } from 'common/Color'; +import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils'; // Work variables to avoid garbage collection let $fg = 0; @@ -65,11 +67,11 @@ export class CellColorResolver { // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasBg = true; } if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasFg = true; } }); @@ -77,10 +79,94 @@ export class CellColorResolver { // Apply the selection color if needed $isSelected = this._selectionRenderModel.isCellSelected(this._terminal, x, y); if ($isSelected) { - $bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + // If the cell has a bg color, retain the color by blending it with the selection color + if ( + (this.result.fg & FgFlags.INVERSE) || + (this.result.bg & Attributes.CM_MASK) !== Attributes.CM_DEFAULT + ) { + // Resolve the standard bg color + if (this.result.fg & FgFlags.INVERSE) { + switch (this.result.fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $bg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $bg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF; + break; + case Attributes.CM_DEFAULT: + default: + $bg = this._themeService.colors.foreground.rgba; + } + } else { + switch (this.result.bg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $bg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $bg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF; + break; + // No need to consider default bg color here as it's not possible + } + } + // Blend with selection bg color + $bg = rgba.blend( + $bg, + ((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80 + ) >> 8 & Attributes.RGB_MASK; + } else { + $bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK; + } $hasBg = true; + + // Apply explicit selection foreground if present if ($colors.selectionForeground) { - $fg = $colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $fg = $colors.selectionForeground.rgba >> 8 & Attributes.RGB_MASK; + $hasFg = true; + } + + // Overwrite fg as bg if it's a special decorative glyph (eg. powerline) + if (treatGlyphAsBackgroundColor(cell.getCode())) { + // Inverse default background should be treated as transparent + if ( + (this.result.fg & FgFlags.INVERSE) && + (this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT + ) { + $fg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK; + } else { + + if (this.result.fg & FgFlags.INVERSE) { + switch (this.result.bg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $fg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $fg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF; + break; + // No need to consider default bg color here as it's not possible + } + } else { + switch (this.result.fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $fg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $fg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF; + break; + case Attributes.CM_DEFAULT: + default: + $fg = this._themeService.colors.foreground.rgba; + } + } + + $fg = rgba.blend( + $fg, + ((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80 + ) >> 8 & Attributes.RGB_MASK; + } $hasFg = true; } } @@ -88,11 +174,11 @@ export class CellColorResolver { // Apply decorations on the top layer this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasBg = true; } if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasFg = true; } }); @@ -119,7 +205,7 @@ export class CellColorResolver { if ($hasBg && !$hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($colors.background.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this.result.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); } @@ -128,7 +214,7 @@ export class CellColorResolver { if (!$hasBg && $hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this.result.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($colors.foreground.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this.result.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); } diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 59b87b0e..9a4bffe0 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -27,7 +27,7 @@ function isBoxOrBlockGlyph(codepoint: number): boolean { return 0x2500 <= codepoint && codepoint <= 0x259F; } -export function excludeFromContrastRatioDemands(codepoint: number): boolean { +export function treatGlyphAsBackgroundColor(codepoint: number): boolean { return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint); } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index f3f67b8b..7ed28b3d 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -6,7 +6,7 @@ import { IColorContrastCache } from 'browser/Types'; import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { computeNextVariantOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; @@ -492,7 +492,7 @@ export class TextureAtlas implements ITextureAtlas { const powerlineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0)); const restrictedPowerlineGlyph = chars.length === 1 && isRestrictedPowerlineGlyph(chars.charCodeAt(0)); - const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, excludeFromContrastRatioDemands(chars.charCodeAt(0))); + const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, treatGlyphAsBackgroundColor(chars.charCodeAt(0))); this._tmpCtx.fillStyle = foregroundColor.css; // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129) diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index 082c81c3..e250950d 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -271,6 +271,27 @@ describe('Color', () => { }); describe('rgba', () => { + describe('blend', () => { + it('should blend colors based on the alpha channel', () => { + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF00), 0x000000FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF10), 0x101010FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF20), 0x202020FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF30), 0x303030FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF40), 0x404040FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF50), 0x505050FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF60), 0x606060FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF70), 0x707070FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF80), 0x808080FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF90), 0x909090FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFA0), 0xA0A0A0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFB0), 0xB0B0B0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFC0), 0xC0C0C0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFD0), 0xD0D0D0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFE0), 0xE0E0E0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFF0), 0xF0F0F0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFFF), 0xFFFFFFFF); + }); + }); describe('ensureContrastRatio', () => { it('should return undefined if the color already meets the contrast ratio (black bg)', () => { assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 1), undefined); diff --git a/src/common/Color.ts b/src/common/Color.ts index 9bfed4e6..2291b7be 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -245,6 +245,23 @@ export namespace rgb { * Helper functions where the source type is "rgba" (number: 0xrrggbbaa). */ export namespace rgba { + export function blend(bg: number, fg: number): number { + $a = (fg & 0xFF) / 0xFF; + if ($a === 1) { + return fg; + } + const fgR = (fg >> 24) & 0xFF; + const fgG = (fg >> 16) & 0xFF; + const fgB = (fg >> 8) & 0xFF; + const bgR = (bg >> 24) & 0xFF; + const bgG = (bg >> 16) & 0xFF; + const bgB = (bg >> 8) & 0xFF; + $r = bgR + Math.round((fgR - bgR) * $a); + $g = bgG + Math.round((fgG - bgG) * $a); + $b = bgB + Math.round((fgB - bgB) * $a); + return channels.toRgba($r, $g, $b); + } + /** * Given a foreground color and a background color, either increase or reduce the luminance of the * foreground color until the specified contrast ratio is met. If pure white or black is hit diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 77bf7941..1381abea 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -8,7 +8,10 @@ import { ITestContext, createTestContext, openTerminal } from './TestUtils'; import { ISharedRendererTestContext, injectSharedRendererTestsStandalone, injectSharedRendererTests } from './SharedRendererTests'; let ctx: ITestContext; -const ctxWrapper: ISharedRendererTestContext = { value: undefined } as any; +const ctxWrapper: ISharedRendererTestContext = { + value: undefined, + skipCanvasExceptions: true +} as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); ctxWrapper.value = ctx; diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index aa747277..d566acd9 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -11,6 +11,7 @@ import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate } f export interface ISharedRendererTestContext { value: ITestContext; skipCanvasExceptions?: boolean; + skipDomExceptions?: boolean; } export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void { @@ -945,7 +946,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 255, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 255, 0, 255]); - await ctx.value.page.evaluate(`window.term.selectAll()`); + await ctx.value.proxy.selectAll(); frameDetails = undefined; // Selection only cell needs to be first to ensure renderer has kicked in await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); @@ -965,7 +966,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void // Check both the cursor line and another line await ctx.value.proxy.writeln('_ '); await ctx.value.proxy.write('_ '); - await ctx.value.page.evaluate(`window.term.selectAll()`); + await ctx.value.proxy.selectAll(); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [128, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [128, 0, 0, 255]); @@ -980,6 +981,44 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); }); + (ctx.skipCanvasExceptions || ctx.skipDomExceptions ? test.describe.skip : test.describe)('selection blending', () => { + test('background', async () => { + const theme: ITheme = { + red: '#CC0000', + selectionBackground: '#FFFFFF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + await ctx.value.proxy.writeln('\x1b[41m red bg'); + await ctx.value.proxy.writeln('\x1b[7m inverse'); + await ctx.value.proxy.writeln('\x1b[31;7m red fg inverse'); + await ctx.value.proxy.selectAll(); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [230,128,128,255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [255,255,255,255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [230,128,128,255]); + }); + test('powerline decorative symbols', async () => { + const theme: ITheme = { + red: '#CC0000', + green: '#00CC00', + selectionBackground: '#FFFFFF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + await ctx.value.proxy.writeln('\u{E0B4} plain\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[31;42m\u{E0B4} red fg green bg\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[32;41m\u{E0B4} green fg red bg\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[31;42;7m\u{E0B4} red fg green bg inverse\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[32;41;7m\u{E0B4} green fg red bg inverse\x1b[0m'); + await ctx.value.proxy.selectAll(); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255,255,255,255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [230, 128, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [128, 230, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 4), [128, 230, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 5), [230, 128, 128, 255]); + }); + }); + test.describe('allowTransparency', async () => { test.beforeEach(() => ctx.value.page.evaluate(`term.options.allowTransparency = true`)); @@ -1003,7 +1042,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); const data = `\x1b[7m■\x1b[0m`; await ctx.value.proxy.write( data); - await ctx.value.page.evaluate(`window.term.selectAll()`); + await ctx.value.proxy.selectAll(); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); }); }); @@ -1206,30 +1245,32 @@ enum CellColorPosition { * treatment. */ export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext): void { - test.beforeEach(async () => { - // Recreate terminal - await openTerminal(ctx.value); - ctx.value.page.evaluate(` - window.term.options.minimumContrastRatio = 1; - window.term.options.allowTransparency = false; - window.term.options.theme = undefined; - `); - // Clear the cached screenshot before each test - frameDetails = undefined; - }); - test.describe('regression tests', () => { - test('#4790: cursor should not be displayed before focusing', async () => { - const theme: ITheme = { - cursor: '#0000FF' - }; - await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); - await ctx.value.proxy.focus(); + test.describe('standalone tests', () => { + test.beforeEach(async () => { + // Recreate terminal + await openTerminal(ctx.value); + ctx.value.page.evaluate(` + window.term.options.minimumContrastRatio = 1; + window.term.options.allowTransparency = false; + window.term.options.theme = undefined; + `); + // Clear the cached screenshot before each test frameDetails = undefined; - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); - await ctx.value.proxy.blur(); - frameDetails = undefined; - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); + test.describe('regression tests', () => { + test('#4790: cursor should not be displayed before focusing', async () => { + const theme: ITheme = { + cursor: '#0000FF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + await ctx.value.proxy.focus(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + await ctx.value.proxy.blur(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); }); }); } From 7bee0018684550d1cf8271462b884507a19b56a1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:18:44 -0800 Subject: [PATCH 2/8] Ensure addons are loaded in standalone tests --- addons/addon-canvas/test/CanvasRenderer.test.ts | 7 ++++++- test/playwright/Renderer.test.ts | 2 +- test/playwright/SharedRendererTests.ts | 5 +++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/addons/addon-canvas/test/CanvasRenderer.test.ts b/addons/addon-canvas/test/CanvasRenderer.test.ts index 2782081c..c8b35c7a 100644 --- a/addons/addon-canvas/test/CanvasRenderer.test.ts +++ b/addons/addon-canvas/test/CanvasRenderer.test.ts @@ -28,5 +28,10 @@ test.describe('Canvas Renderer Integration Tests', () => { test.skip(({ browserName }) => browserName === 'webkit'); injectSharedRendererTests(ctxWrapper); - injectSharedRendererTestsStandalone(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper, async () => { + await ctx.page.evaluate(` + window.addon = new window.CanvasAddon(true); + window.term.loadAddon(window.addon); + `); + }); }); diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 1381abea..0d0159bd 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -21,5 +21,5 @@ test.afterAll(async () => await ctx.page.close()); test.describe('DOM Renderer Integration Tests', () => { injectSharedRendererTests(ctxWrapper); - injectSharedRendererTestsStandalone(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper, () => {}); }); diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index d566acd9..0b7dac1c 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1244,16 +1244,17 @@ enum CellColorPosition { * This is much slower than just calling `Terminal.reset` but testing some features needs this * treatment. */ -export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext): void { +export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext, setupCb: () => Promise | void): void { test.describe('standalone tests', () => { test.beforeEach(async () => { // Recreate terminal await openTerminal(ctx.value); - ctx.value.page.evaluate(` + await ctx.value.page.evaluate(` window.term.options.minimumContrastRatio = 1; window.term.options.allowTransparency = false; window.term.options.theme = undefined; `); + await setupCb(); // Clear the cached screenshot before each test frameDetails = undefined; }); From 66b426311e2ed25bf7f3dab086794fe48367e832 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:21:18 -0800 Subject: [PATCH 3/8] Fix compile --- addons/addon-webgl/test/WebglRenderer.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/addon-webgl/test/WebglRenderer.test.ts b/addons/addon-webgl/test/WebglRenderer.test.ts index 4b73c08b..ad0e7e73 100644 --- a/addons/addon-webgl/test/WebglRenderer.test.ts +++ b/addons/addon-webgl/test/WebglRenderer.test.ts @@ -29,5 +29,10 @@ test.describe('WebGL Renderer Integration Tests', async () => { } injectSharedRendererTests(ctxWrapper); - injectSharedRendererTestsStandalone(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper, async () => { + await ctx.page.evaluate(` + window.addon = new window.WebglAddon(true); + window.term.loadAddon(window.addon); + `); + }); }); From 9b62849bb8566e5ee796869048a06775c38719e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:23:21 -0800 Subject: [PATCH 4/8] Fix lint --- addons/addon-webgl/test/WebglRenderer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/addon-webgl/test/WebglRenderer.test.ts b/addons/addon-webgl/test/WebglRenderer.test.ts index ad0e7e73..d5f62fda 100644 --- a/addons/addon-webgl/test/WebglRenderer.test.ts +++ b/addons/addon-webgl/test/WebglRenderer.test.ts @@ -30,7 +30,7 @@ test.describe('WebGL Renderer Integration Tests', async () => { injectSharedRendererTests(ctxWrapper); injectSharedRendererTestsStandalone(ctxWrapper, async () => { - await ctx.page.evaluate(` + await ctx.page.evaluate(` window.addon = new window.WebglAddon(true); window.term.loadAddon(window.addon); `); From 78a945ae9efc527c6b517c2419e7a349cfe25ecd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:34:16 -0800 Subject: [PATCH 5/8] Suppress test failure --- test/playwright/SharedRendererTests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 0b7dac1c..7291f42f 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1180,7 +1180,8 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); }); - (ctx.skipCanvasExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on selected inverse text', async () => { + // HACK: It's not clear why DOM is failing here + (ctx.skipCanvasExceptions || ctx.skipDomExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on selected inverse text', async () => { const theme: ITheme = { foreground: '#777777', background: '#555555', From c0fac5e5f1c386024427ca7bdd7eb940e2291d04 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:38:55 -0800 Subject: [PATCH 6/8] Fix test suppression --- test/playwright/SharedRendererTests.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 7291f42f..005f11da 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1165,7 +1165,8 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]); }); - test('#4759: minimum contrast ratio should be respected on inverse text', async () => { + // HACK: It's not clear why DOM is failing here + (ctx.skipDomExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on inverse text', async () => { const theme: ITheme = { foreground: '#aaaaaa', background: '#333333' @@ -1180,8 +1181,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); }); - // HACK: It's not clear why DOM is failing here - (ctx.skipCanvasExceptions || ctx.skipDomExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on selected inverse text', async () => { + (ctx.skipCanvasExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on selected inverse text', async () => { const theme: ITheme = { foreground: '#777777', background: '#555555', From b843e0fc8e5f7e0e01e526cc7100fa6a9ba9a190 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:44:14 -0800 Subject: [PATCH 7/8] Pass through dom skip exceptions flag --- test/playwright/Renderer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 0d0159bd..119832d6 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -10,7 +10,7 @@ import { ISharedRendererTestContext, injectSharedRendererTestsStandalone, inject let ctx: ITestContext; const ctxWrapper: ISharedRendererTestContext = { value: undefined, - skipCanvasExceptions: true + skipDomExceptions: true } as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); From a4505ed724f5a0041d69d86067a0d32b85e6d094 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:47:54 -0800 Subject: [PATCH 8/8] Skip another test on canvas --- test/playwright/SharedRendererTests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 005f11da..657fba44 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1131,7 +1131,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); test.describe('regression tests', () => { - test('#4736: inactive selection background should replace regular cell background color', async () => { + (ctx.skipCanvasExceptions ? test.skip : test)('#4736: inactive selection background should replace regular cell background color', async () => { const theme: ITheme = { selectionBackground: '#FF0000', selectionInactiveBackground: '#0000FF'