fix(desktop): keep browser surfaces interactive
This commit is contained in:
@@ -58,7 +58,6 @@ import {
|
||||
useBrowserStore,
|
||||
} from "@/desktop/browser/store";
|
||||
import {
|
||||
applyBrowserWebviewViewport,
|
||||
applyInactiveBrowserWebviewViewport,
|
||||
isResidentBrowserWebviewReady,
|
||||
prepareBrowserWebview,
|
||||
@@ -787,8 +786,7 @@ export function BrowserPane({
|
||||
}
|
||||
releaseResidentBrowserWebview(browserId, webview);
|
||||
if (isPresentedRef.current) {
|
||||
applyBrowserWebviewViewport(webview, browserViewportRef.current);
|
||||
presentBrowserWebview(browserId, webview, host, clip);
|
||||
presentBrowserWebview(browserId, webview, host, clip, browserViewportRef.current);
|
||||
} else {
|
||||
applyInactiveBrowserWebviewViewport(browserId, webview, browserViewportRef.current);
|
||||
}
|
||||
@@ -799,7 +797,13 @@ export function BrowserPane({
|
||||
if (!isPresentedRef.current) {
|
||||
return;
|
||||
}
|
||||
presentBrowserWebview(browserIdRef.current, webview, host, clip);
|
||||
presentBrowserWebview(
|
||||
browserIdRef.current,
|
||||
webview,
|
||||
host,
|
||||
clip,
|
||||
browserViewportRef.current,
|
||||
);
|
||||
rememberResolvedBrowserWebviewSize(browserIdRef.current, webview);
|
||||
});
|
||||
|
||||
@@ -882,6 +886,13 @@ export function BrowserPane({
|
||||
};
|
||||
const handleWebviewFocus = () => {
|
||||
onFocusPane?.();
|
||||
webview.focus?.();
|
||||
const focusBrowser = getDesktopHost()?.browser?.focus;
|
||||
if (typeof focusBrowser === "function") {
|
||||
void focusBrowser(browserIdRef.current).catch((error) => {
|
||||
console.error("[browser-webview] focus failed", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
webview.addEventListener("did-start-loading", handleStartLoading);
|
||||
@@ -946,11 +957,10 @@ export function BrowserPane({
|
||||
releaseResidentBrowserWebview(browserId, webview);
|
||||
return;
|
||||
}
|
||||
applyBrowserWebviewViewport(webview, browserViewport);
|
||||
const host = webviewHostRef.current;
|
||||
const clip = webviewClipRef.current;
|
||||
if (host && clip) {
|
||||
presentBrowserWebview(browserId, webview, host, clip);
|
||||
presentBrowserWebview(browserId, webview, host, clip, browserViewport);
|
||||
}
|
||||
if (browserViewport.mode === "fixed") {
|
||||
rememberBrowserWebviewSize({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
applyBrowserWebviewViewport,
|
||||
applyInactiveBrowserWebviewViewport,
|
||||
type BrowserWebviewProfileHost,
|
||||
clearResidentBrowserWebviewsForTests,
|
||||
@@ -141,7 +140,9 @@ describe("resident browser webviews", () => {
|
||||
}
|
||||
const permanentParent = webview.parentElement;
|
||||
|
||||
presentBrowserWebview("browser-stable-parent", webview, anchor, clip);
|
||||
presentBrowserWebview("browser-stable-parent", webview, anchor, clip, {
|
||||
mode: "responsive",
|
||||
});
|
||||
expect(webview.parentElement).toBe(permanentParent);
|
||||
expect(permanentParent.style.left).toBe("40px");
|
||||
expect(permanentParent.style.top).toBe("60px");
|
||||
@@ -149,12 +150,17 @@ describe("resident browser webviews", () => {
|
||||
expect(permanentParent.style.height).toBe("480px");
|
||||
expect(permanentParent.style.pointerEvents).toBe("auto");
|
||||
expect(permanentParent.getAttribute("aria-hidden")).toBe("false");
|
||||
expect(webview.style.flex).toBe("0 0 auto");
|
||||
expect(webview.style.width).toBe("640px");
|
||||
expect(webview.style.height).toBe("480px");
|
||||
|
||||
releaseResidentBrowserWebview("browser-stable-parent", webview);
|
||||
expect(webview.parentElement).toBe(permanentParent);
|
||||
expectParkedSurface(permanentParent);
|
||||
|
||||
presentBrowserWebview("browser-stable-parent", webview, anchor, clip);
|
||||
presentBrowserWebview("browser-stable-parent", webview, anchor, clip, {
|
||||
mode: "responsive",
|
||||
});
|
||||
expect(webview.parentElement).toBe(permanentParent);
|
||||
});
|
||||
|
||||
@@ -167,7 +173,6 @@ describe("resident browser webviews", () => {
|
||||
if (!webview?.parentElement) {
|
||||
throw new Error("Expected resident browser surface");
|
||||
}
|
||||
applyBrowserWebviewViewport(webview, { mode: "fixed", width: 2560, height: 1440 });
|
||||
const anchor = document.createElement("div");
|
||||
const clip = document.createElement("div");
|
||||
Object.defineProperty(anchor, "getBoundingClientRect", {
|
||||
@@ -177,7 +182,11 @@ describe("resident browser webviews", () => {
|
||||
value: () => ({ left: 100, top: 150, width: 800, height: 600 }),
|
||||
});
|
||||
|
||||
presentBrowserWebview("browser-oversized", webview, anchor, clip);
|
||||
presentBrowserWebview("browser-oversized", webview, anchor, clip, {
|
||||
mode: "fixed",
|
||||
width: 2560,
|
||||
height: 1440,
|
||||
});
|
||||
|
||||
expect(webview.parentElement.style.left).toBe("100px");
|
||||
expect(webview.parentElement.style.top).toBe("150px");
|
||||
@@ -359,8 +368,20 @@ describe("resident browser webviews", () => {
|
||||
|
||||
it("applies an exact fixed viewport without a flex width override", () => {
|
||||
const webview = document.createElement("webview");
|
||||
const anchor = document.createElement("div");
|
||||
const clip = document.createElement("div");
|
||||
Object.defineProperty(anchor, "getBoundingClientRect", {
|
||||
value: () => ({ left: 0, top: 0, width: 640, height: 480 }),
|
||||
});
|
||||
Object.defineProperty(clip, "getBoundingClientRect", {
|
||||
value: () => ({ left: 0, top: 0, width: 640, height: 480 }),
|
||||
});
|
||||
|
||||
applyBrowserWebviewViewport(webview, { mode: "fixed", width: 640, height: 480 });
|
||||
presentBrowserWebview("browser-fixed", webview, anchor, clip, {
|
||||
mode: "fixed",
|
||||
width: 640,
|
||||
height: 480,
|
||||
});
|
||||
|
||||
expect(webview.style.flex).toBe("0 0 auto");
|
||||
expect(webview.style.width).toBe("640px");
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type DesktopBrowserBridge,
|
||||
} from "@/desktop/host";
|
||||
import type { BrowserViewport } from "@/desktop/browser/store";
|
||||
import { WEB_SURFACE_PLANE } from "@/lib/overlay-root";
|
||||
|
||||
const RESIDENT_BROWSER_HOST_ID = "paseo-browser-resident-webviews";
|
||||
const BROWSER_ID_ATTRIBUTE = "data-paseo-browser-id";
|
||||
@@ -108,7 +109,7 @@ function applyResidentHostParkingStyle(host: HTMLElement): void {
|
||||
host.style.opacity = "1";
|
||||
host.style.pointerEvents = "none";
|
||||
host.style.display = "block";
|
||||
host.style.zIndex = "0";
|
||||
host.style.zIndex = String(WEB_SURFACE_PLANE.browser);
|
||||
host.style.clipPath = "";
|
||||
host.style.visibility = "visible";
|
||||
host.style.transform = "";
|
||||
@@ -220,24 +221,16 @@ export function rememberBrowserWebviewSize(input: {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
function applyBrowserWebviewDimensions(webview: HTMLElement, viewport: BrowserViewport): void {
|
||||
function applyBrowserWebviewDimensions(
|
||||
webview: HTMLElement,
|
||||
dimensions: { width: number; height: number },
|
||||
): void {
|
||||
webview.style.display = "flex";
|
||||
webview.style.border = "0";
|
||||
webview.style.background = "transparent";
|
||||
if (viewport.mode === "responsive") {
|
||||
webview.style.flex = "1";
|
||||
webview.style.width = "100%";
|
||||
webview.style.height = "100%";
|
||||
return;
|
||||
}
|
||||
webview.style.flex = "0 0 auto";
|
||||
webview.style.width = `${viewport.width}px`;
|
||||
webview.style.height = `${viewport.height}px`;
|
||||
}
|
||||
|
||||
export function applyBrowserWebviewViewport(webview: HTMLElement, viewport: BrowserViewport): void {
|
||||
clearResidentWebviewParkingStyle(webview);
|
||||
applyBrowserWebviewDimensions(webview, viewport);
|
||||
webview.style.width = `${Math.max(1, Math.round(dimensions.width))}px`;
|
||||
webview.style.height = `${Math.max(1, Math.round(dimensions.height))}px`;
|
||||
}
|
||||
|
||||
export function applyInactiveBrowserWebviewViewport(
|
||||
@@ -256,6 +249,7 @@ export function presentBrowserWebview(
|
||||
webview: HTMLElement,
|
||||
anchor: HTMLElement,
|
||||
clip: HTMLElement,
|
||||
viewport: BrowserViewport,
|
||||
): void {
|
||||
const normalizedBrowserId = trimNonEmpty(browserId);
|
||||
if (!normalizedBrowserId) {
|
||||
@@ -297,6 +291,13 @@ export function presentBrowserWebview(
|
||||
surface.style.pointerEvents = hasVisibleArea ? "auto" : "none";
|
||||
surface.style.display = "flex";
|
||||
surface.style.visibility = "visible";
|
||||
clearResidentWebviewParkingStyle(webview);
|
||||
applyBrowserWebviewDimensions(
|
||||
webview,
|
||||
viewport.mode === "responsive"
|
||||
? { width: anchorBounds.width, height: anchorBounds.height }
|
||||
: viewport,
|
||||
);
|
||||
webview.style.position = "absolute";
|
||||
webview.style.left = `${Math.round(anchorBounds.left - surfaceLeft)}px`;
|
||||
webview.style.top = `${Math.round(anchorBounds.top - surfaceTop)}px`;
|
||||
@@ -432,7 +433,7 @@ export function resizeResidentBrowserWebview(input: {
|
||||
const ownerDocument = readDocument();
|
||||
const webview = ownerDocument ? findBrowserWebview(normalizedBrowserId, ownerDocument) : null;
|
||||
if (webview) {
|
||||
applyBrowserWebviewDimensions(webview, { mode: "fixed", ...dimensions });
|
||||
applyBrowserWebviewDimensions(webview, dimensions);
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
|
||||
@@ -151,6 +151,7 @@ export interface DesktopBrowserBridge {
|
||||
workspaceId: string;
|
||||
browserId: string | null;
|
||||
}) => Promise<void>;
|
||||
focus?: (browserId: string) => Promise<boolean>;
|
||||
openDevTools?: (browserId: string) => Promise<unknown>;
|
||||
clearProfile?: (legacyBrowserIds: string[]) => Promise<void>;
|
||||
executeAutomationCommand?: (
|
||||
|
||||
@@ -28,14 +28,20 @@ export function getOverlayRoot(): HTMLElement {
|
||||
if (!el) {
|
||||
el = document.createElement("div");
|
||||
el.id = "overlay-root";
|
||||
el.style.position = "fixed";
|
||||
el.style.inset = "0";
|
||||
el.style.pointerEvents = "none";
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
el.style.position = "fixed";
|
||||
el.style.inset = "0";
|
||||
el.style.pointerEvents = "none";
|
||||
el.style.zIndex = String(WEB_SURFACE_PLANE.overlay);
|
||||
return el;
|
||||
}
|
||||
|
||||
export const WEB_SURFACE_PLANE = {
|
||||
browser: 0,
|
||||
overlay: 1,
|
||||
} as const;
|
||||
|
||||
export const OVERLAY_Z = {
|
||||
floating: 10,
|
||||
modal: 20,
|
||||
|
||||
Reference in New Issue
Block a user