Fix multi-webview browser capture prep
This commit is contained in:
@@ -28,10 +28,18 @@ describe("resident browser webviews", () => {
|
||||
expect(webview.style.display).toBe("inline-flex");
|
||||
expect(webview.style.width).toBe("1280px");
|
||||
expect(webview.style.height).toBe("800px");
|
||||
expect(webview.style.position).toBe("absolute");
|
||||
expect(webview.style.left).toBe("0px");
|
||||
expect(webview.style.top).toBe("0px");
|
||||
expect(webview.style.zIndex).toBe("0");
|
||||
|
||||
const reused = takeResidentBrowserWebview("browser-a");
|
||||
|
||||
expect(reused).toBe(webview);
|
||||
expect(webview.style.position).toBe("");
|
||||
expect(webview.style.left).toBe("");
|
||||
expect(webview.style.top).toBe("");
|
||||
expect(webview.style.zIndex).toBe("");
|
||||
expect(takeResidentBrowserWebview("browser-a")).toBeNull();
|
||||
});
|
||||
|
||||
@@ -87,6 +95,10 @@ describe("resident browser webviews", () => {
|
||||
expect(webview.style.display).toBe("inline-flex");
|
||||
expect(webview.style.width).toBe("1280px");
|
||||
expect(webview.style.height).toBe("800px");
|
||||
expect(webview.style.position).toBe("absolute");
|
||||
expect(webview.style.left).toBe("0px");
|
||||
expect(webview.style.top).toBe("0px");
|
||||
expect(webview.style.zIndex).toBe("2");
|
||||
|
||||
await restoreResidentBrowserWebviewAfterPixelCapture(preparation);
|
||||
|
||||
@@ -94,6 +106,42 @@ describe("resident browser webviews", () => {
|
||||
expect(host?.style.width).toBe("1280px");
|
||||
expect(host?.style.height).toBe("800px");
|
||||
expect(host?.style.opacity).toBe("0");
|
||||
expect(webview.style.zIndex).toBe("0");
|
||||
});
|
||||
|
||||
it("parks resident webviews as an overlapping stack and raises the capture target", async () => {
|
||||
const firstWebview = ensureResidentBrowserWebview({
|
||||
browserId: "browser-first",
|
||||
url: "https://example.com/first",
|
||||
});
|
||||
const secondWebview = ensureResidentBrowserWebview({
|
||||
browserId: "browser-second",
|
||||
url: "https://example.com/second",
|
||||
});
|
||||
if (!firstWebview || !secondWebview) {
|
||||
throw new Error("Expected resident browser webviews");
|
||||
}
|
||||
|
||||
expect(firstWebview.style.position).toBe("absolute");
|
||||
expect(firstWebview.style.left).toBe("0px");
|
||||
expect(firstWebview.style.top).toBe("0px");
|
||||
expect(firstWebview.style.zIndex).toBe("0");
|
||||
expect(secondWebview.style.position).toBe("absolute");
|
||||
expect(secondWebview.style.left).toBe("0px");
|
||||
expect(secondWebview.style.top).toBe("0px");
|
||||
expect(secondWebview.style.zIndex).toBe("0");
|
||||
|
||||
const preparation = await prepareResidentBrowserWebviewForPixelCapture({
|
||||
browserId: "browser-second",
|
||||
});
|
||||
|
||||
expect(firstWebview.style.zIndex).toBe("0");
|
||||
expect(secondWebview.style.zIndex).toBe("2");
|
||||
|
||||
await restoreResidentBrowserWebviewAfterPixelCapture(preparation);
|
||||
|
||||
expect(firstWebview.style.zIndex).toBe("0");
|
||||
expect(secondWebview.style.zIndex).toBe("0");
|
||||
});
|
||||
|
||||
it("keeps the resident host paintable until every capture token is restored", async () => {
|
||||
|
||||
@@ -20,6 +20,7 @@ interface ActiveCapturePreparation {
|
||||
browserId: string;
|
||||
requestId?: string;
|
||||
preparesResidentHost: boolean;
|
||||
webview: HTMLElement;
|
||||
}
|
||||
|
||||
function trimNonEmpty(value: string | null | undefined): string | null {
|
||||
@@ -98,15 +99,37 @@ function applyResidentWebviewStyle(webview: HTMLElement): void {
|
||||
webview.style.height = `${RESIDENT_VIEWPORT_HEIGHT}px`;
|
||||
webview.style.border = "0";
|
||||
webview.style.background = "transparent";
|
||||
webview.style.position = "absolute";
|
||||
webview.style.left = "0";
|
||||
webview.style.top = "0";
|
||||
webview.style.marginTop = "0";
|
||||
webview.style.zIndex = "0";
|
||||
}
|
||||
|
||||
function hasActiveResidentHostPreparation(): boolean {
|
||||
for (const preparation of activeCapturePreparations.values()) {
|
||||
if (preparation.preparesResidentHost) {
|
||||
return true;
|
||||
function clearResidentWebviewParkingStyle(webview: HTMLElement): void {
|
||||
webview.style.position = "";
|
||||
webview.style.left = "";
|
||||
webview.style.top = "";
|
||||
webview.style.marginTop = "";
|
||||
webview.style.zIndex = "";
|
||||
}
|
||||
|
||||
function residentWebviewChildren(host: HTMLElement): HTMLElement[] {
|
||||
const webviews: HTMLElement[] = [];
|
||||
for (const element of host.querySelectorAll(`[${BROWSER_ID_ATTRIBUTE}]`)) {
|
||||
if (element instanceof HTMLElement) {
|
||||
webviews.push(element);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return webviews;
|
||||
}
|
||||
|
||||
function raiseResidentWebviewForCapture(host: HTMLElement, target: HTMLElement): void {
|
||||
for (const webview of residentWebviewChildren(host)) {
|
||||
applyResidentWebviewStyle(webview);
|
||||
}
|
||||
applyResidentWebviewStyle(target);
|
||||
target.style.zIndex = "2";
|
||||
}
|
||||
|
||||
function nextAnimationFrame(): Promise<void> {
|
||||
@@ -143,13 +166,32 @@ function activeCaptureTokenFor(input: { token?: string; requestId?: string }): s
|
||||
return null;
|
||||
}
|
||||
|
||||
function parkResidentHostIfIdle(): void {
|
||||
if (hasActiveResidentHostPreparation()) {
|
||||
function latestActiveResidentHostPreparation(): ActiveCapturePreparation | null {
|
||||
let latest: ActiveCapturePreparation | null = null;
|
||||
for (const preparation of activeCapturePreparations.values()) {
|
||||
if (preparation.preparesResidentHost) {
|
||||
latest = preparation;
|
||||
}
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
function syncResidentHostCaptureState(): void {
|
||||
const host = readDocument()?.getElementById(RESIDENT_BROWSER_HOST_ID);
|
||||
if (!(host instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const host = readDocument()?.getElementById(RESIDENT_BROWSER_HOST_ID);
|
||||
if (host instanceof HTMLElement) {
|
||||
applyResidentHostParkingStyle(host);
|
||||
|
||||
const latest = latestActiveResidentHostPreparation();
|
||||
if (latest) {
|
||||
applyResidentHostCaptureStyle(host);
|
||||
raiseResidentWebviewForCapture(host, latest.webview);
|
||||
return;
|
||||
}
|
||||
|
||||
applyResidentHostParkingStyle(host);
|
||||
for (const webview of residentWebviewChildren(host)) {
|
||||
applyResidentWebviewStyle(webview);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +202,8 @@ function releaseCapturePreparationToken(token: string): void {
|
||||
}
|
||||
activeCapturePreparations.delete(token);
|
||||
if (preparation.preparesResidentHost) {
|
||||
parkResidentHostIfIdle();
|
||||
applyResidentWebviewStyle(preparation.webview);
|
||||
syncResidentHostCaptureState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,12 +213,12 @@ function releaseCapturePreparationsForBrowser(browserId: string): void {
|
||||
activeCapturePreparations.delete(token);
|
||||
}
|
||||
}
|
||||
parkResidentHostIfIdle();
|
||||
syncResidentHostCaptureState();
|
||||
}
|
||||
|
||||
function releaseAllCapturePreparations(): void {
|
||||
activeCapturePreparations.clear();
|
||||
parkResidentHostIfIdle();
|
||||
syncResidentHostCaptureState();
|
||||
}
|
||||
|
||||
export function prepareBrowserWebview(
|
||||
@@ -233,6 +276,7 @@ export function takeResidentBrowserWebview(browserId: string): HTMLElement | nul
|
||||
}
|
||||
|
||||
residentWebviewsByBrowserId.delete(normalizedBrowserId);
|
||||
clearResidentWebviewParkingStyle(webview);
|
||||
return webview;
|
||||
}
|
||||
|
||||
@@ -278,11 +322,12 @@ export async function prepareResidentBrowserWebviewForPixelCapture(input: {
|
||||
browserId,
|
||||
...(requestId ? { requestId } : {}),
|
||||
preparesResidentHost,
|
||||
webview,
|
||||
});
|
||||
try {
|
||||
if (preparesResidentHost) {
|
||||
applyResidentHostCaptureStyle(host);
|
||||
applyResidentWebviewStyle(webview);
|
||||
raiseResidentWebviewForCapture(host, webview);
|
||||
}
|
||||
await waitForCapturePaint(webview);
|
||||
if (!activeCapturePreparations.has(token)) {
|
||||
|
||||
Reference in New Issue
Block a user