mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 22:23:07 +00:00
fix(desktop): capture the annotated element when the app is zoomed (#5703)
The browser pane measures the selected element in page CSS pixels, but capturePage takes device-independent pixels. Paseo's zoom carries into the in-app browser, so at any zoom other than 100% the screenshot attached to an annotation was offset and undersized. Scale the region by the guest's zoom factor before capturing. Closes #5690 Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -29,7 +29,7 @@ function harness(guest: BrowserCaptureGuest | null = null) {
|
||||
describe("browser capture service", () => {
|
||||
it("validates and rounds guest-relative bounds before capture", async () => {
|
||||
const capturePage = vi.fn(async () => image());
|
||||
const { service } = harness({ isDestroyed: () => false, capturePage });
|
||||
const { service } = harness({ isDestroyed: () => false, getZoomFactor: () => 1, capturePage });
|
||||
|
||||
await expect(
|
||||
service.capture({
|
||||
@@ -41,9 +41,25 @@ describe("browser capture service", () => {
|
||||
expect(capturePage).toHaveBeenCalledWith({ x: 0, y: 9, width: 20, height: 11 });
|
||||
});
|
||||
|
||||
it("captures the selected element when the page is zoomed", async () => {
|
||||
const capturePage = vi.fn(async () => image());
|
||||
const { service } = harness({
|
||||
isDestroyed: () => false,
|
||||
getZoomFactor: () => 1.2,
|
||||
capturePage,
|
||||
});
|
||||
|
||||
await service.capture({
|
||||
browserId: "browser-1",
|
||||
hostWebContentsId: 42,
|
||||
rect: { x: 300, y: 200, width: 120, height: 60 },
|
||||
});
|
||||
expect(capturePage).toHaveBeenCalledWith({ x: 360, y: 240, width: 144, height: 72 });
|
||||
});
|
||||
|
||||
it("rejects invalid or unavailable captures without touching the guest", async () => {
|
||||
const capturePage = vi.fn(async () => image());
|
||||
const { service } = harness({ isDestroyed: () => false, capturePage });
|
||||
const { service } = harness({ isDestroyed: () => false, getZoomFactor: () => 1, capturePage });
|
||||
|
||||
await expect(
|
||||
service.capture({ browserId: "browser-1", hostWebContentsId: 42, rect: { width: 0 } }),
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface BrowserCaptureImage {
|
||||
|
||||
export interface BrowserCaptureGuest<TImage extends BrowserCaptureImage = BrowserCaptureImage> {
|
||||
isDestroyed(): boolean;
|
||||
getZoomFactor(): number;
|
||||
capturePage(rect: BrowserCaptureRect): Promise<TImage>;
|
||||
}
|
||||
|
||||
@@ -40,7 +41,9 @@ export interface BrowserCaptureService {
|
||||
copy(payload: unknown): Promise<boolean>;
|
||||
}
|
||||
|
||||
function captureRect(value: unknown): BrowserCaptureRect | null {
|
||||
// The renderer measures the element in page CSS pixels, while capturePage
|
||||
// takes device-independent pixels, so the page zoom scales the region.
|
||||
function captureRect(value: unknown, zoomFactor: number): BrowserCaptureRect | null {
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
const coordinates = [record.x, record.y, record.width, record.height];
|
||||
@@ -51,7 +54,9 @@ function captureRect(value: unknown): BrowserCaptureRect | null {
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const [x, y, width, height] = coordinates as number[];
|
||||
const [x, y, width, height] = (coordinates as number[]).map(
|
||||
(coordinate) => coordinate * zoomFactor,
|
||||
);
|
||||
if (width <= 0 || height <= 0) return null;
|
||||
return {
|
||||
x: Math.max(0, Math.round(x)),
|
||||
@@ -80,8 +85,9 @@ export function createBrowserCaptureService<TImage extends BrowserCaptureImage>(
|
||||
async capture({ browserId, hostWebContentsId, rect }) {
|
||||
if (typeof browserId !== "string" || browserId.trim().length === 0) return null;
|
||||
const guest = dependencies.findGuest(browserId, hostWebContentsId);
|
||||
const bounds = captureRect(rect);
|
||||
if (!guest || guest.isDestroyed() || !bounds) return null;
|
||||
if (!guest || guest.isDestroyed()) return null;
|
||||
const bounds = captureRect(rect, guest.getZoomFactor());
|
||||
if (!bounds) return null;
|
||||
try {
|
||||
const image = await guest.capturePage(bounds);
|
||||
return image.isEmpty() ? null : image.toDataURL();
|
||||
|
||||
Reference in New Issue
Block a user