xterm's DOM renderer builds a node per cell, which is CPU-bound — the wrong bottleneck on a phone, where an agent dumping output stutters visibly on a Pixel 7 despite plenty of GPU headroom. A WebGL context is scarce and revocable, so it follows the mounted view rather than the connection: acquired in onMount, released in onUnmount. ConnectionPool keeps MAX_PARKED connections alive behind the visible ones, which would otherwise exceed the browser's context cap (~16 on desktop Chrome, fewer on Android) and bring a parked terminal back blank. Context loss falls back to the DOM renderer and retries against a shared two-loss budget; a device that keeps dropping contexts stops being asked. A device pixel ratio change rebuilds the glyph atlas, which is otherwise left blurry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { MAX_CONTEXT_LOSSES, WebglPolicy } from "./webglRenderer.js";
|
|
|
|
describe("WebglPolicy", () => {
|
|
it("is enabled when the browser supports WebGL2", () => {
|
|
expect(new WebglPolicy(() => true).enabled()).toBe(true);
|
|
});
|
|
|
|
it("is disabled when the browser has no WebGL2", () => {
|
|
expect(new WebglPolicy(() => false).enabled()).toBe(false);
|
|
});
|
|
|
|
it("probes only once, however often it is asked", () => {
|
|
const probe = vi.fn(() => true);
|
|
const policy = new WebglPolicy(probe);
|
|
policy.enabled();
|
|
policy.enabled();
|
|
policy.enabled();
|
|
expect(probe).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("tolerates a loss below the budget — a backgrounded tab is ordinary", () => {
|
|
const policy = new WebglPolicy(() => true);
|
|
policy.noteContextLoss();
|
|
expect(policy.enabled()).toBe(true);
|
|
});
|
|
|
|
it("gives up for good once losses reach the budget", () => {
|
|
const policy = new WebglPolicy(() => true);
|
|
for (let i = 0; i < MAX_CONTEXT_LOSSES; i++) policy.noteContextLoss();
|
|
expect(policy.enabled()).toBe(false);
|
|
expect(policy.contextLosses()).toBe(MAX_CONTEXT_LOSSES);
|
|
});
|
|
|
|
it("stays disabled past the budget, and never bothers probing", () => {
|
|
const probe = vi.fn(() => true);
|
|
const policy = new WebglPolicy(probe);
|
|
for (let i = 0; i < MAX_CONTEXT_LOSSES + 3; i++) policy.noteContextLoss();
|
|
expect(policy.enabled()).toBe(false);
|
|
expect(probe).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("treats a throwing probe as unsupported", () => {
|
|
const policy = new WebglPolicy(() => {
|
|
throw new Error("no gl for you");
|
|
});
|
|
expect(() => policy.enabled()).not.toThrow();
|
|
expect(policy.enabled()).toBe(false);
|
|
});
|
|
|
|
it("reports no WebGL2 under the test DOM, which has no GPU", () => {
|
|
expect(new WebglPolicy().enabled()).toBe(false);
|
|
});
|
|
});
|