mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(producer): avoid reviving hidden DOM in HDR layers (#1935)
* fix(producer): avoid reviving hidden DOM in HDR layers * fix(producer): filter transition HDR DOM masks * fix(producer): keep hidden timed descendants masked
This commit is contained in:
@@ -8,6 +8,8 @@ import {
|
||||
injectVideoFramesBatch,
|
||||
syncVideoFrameVisibility,
|
||||
shouldDefaultCaptureBeyondViewport,
|
||||
applyDomLayerMask,
|
||||
removeDomLayerMask,
|
||||
} from "./screenshotService.js";
|
||||
|
||||
// Stub a Page + CDPSession just enough that pageScreenshotCapture can call
|
||||
@@ -546,4 +548,54 @@ describe("video-frame injection respects ancestor visibility", () => {
|
||||
expect(seededImg.style.visibility).toBe("hidden");
|
||||
expect(setPropertySpy).toHaveBeenCalledWith("visibility", "hidden", "important");
|
||||
});
|
||||
|
||||
it("applyDomLayerMask does not revive hidden idless timed descendants of a shown layer", async () => {
|
||||
const { window, document } = parseHTML(
|
||||
`<html><head></head><body>
|
||||
<div id="scene" data-start="0" data-duration="6">
|
||||
<div class="label" data-start="4.5" data-duration="1.5">late label</div>
|
||||
</div>
|
||||
</body></html>`,
|
||||
);
|
||||
const scene = document.getElementById("scene") as HTMLElement;
|
||||
const label = document.querySelector(".label") as HTMLElement;
|
||||
label.style.visibility = "hidden";
|
||||
|
||||
Object.defineProperty(window, "getComputedStyle", {
|
||||
configurable: true,
|
||||
value: (el: Element) => ({
|
||||
display: (el as HTMLElement).style.display || "block",
|
||||
visibility: (el as HTMLElement).style.visibility || "visible",
|
||||
}),
|
||||
});
|
||||
|
||||
const globals = globalThis as unknown as {
|
||||
window?: Window;
|
||||
document?: Document;
|
||||
HTMLElement?: typeof HTMLElement;
|
||||
CSS?: typeof CSS;
|
||||
};
|
||||
const previousWindow = globals.window;
|
||||
const previousDocument = globals.document;
|
||||
const previousHTMLElement = globals.HTMLElement;
|
||||
const previousCSS = globals.CSS;
|
||||
globals.window = window;
|
||||
globals.document = document;
|
||||
globals.HTMLElement = window.HTMLElement;
|
||||
globals.CSS = { escape: (value: string) => value.replace(/[^a-zA-Z0-9_-]/g, "\\$&") } as CSS;
|
||||
try {
|
||||
await applyDomLayerMask(passthroughPage(), ["scene"], []);
|
||||
expect(scene.style.visibility || "").toBe("");
|
||||
expect(label.style.visibility).toBe("hidden");
|
||||
|
||||
await removeDomLayerMask(passthroughPage(), []);
|
||||
expect(label.style.visibility).toBe("hidden");
|
||||
expect(label.hasAttribute("data-hf-dom-layer-mask-hidden")).toBe(false);
|
||||
} finally {
|
||||
globals.window = previousWindow;
|
||||
globals.document = previousDocument;
|
||||
globals.HTMLElement = previousHTMLElement;
|
||||
globals.CSS = previousCSS;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -261,6 +261,9 @@ export async function captureAlphaPng(page: Page, width: number, height: number)
|
||||
* tests can assert presence/absence of the mask between captures.
|
||||
*/
|
||||
export const DOM_LAYER_MASK_STYLE_ID = "__hf_dom_layer_mask__";
|
||||
const DOM_LAYER_MASK_HIDDEN_ATTR = "data-hf-dom-layer-mask-hidden";
|
||||
const DOM_LAYER_MASK_PREV_VISIBILITY_ATTR = "data-hf-dom-layer-mask-prev-visibility";
|
||||
const DOM_LAYER_MASK_PREV_PRIORITY_ATTR = "data-hf-dom-layer-mask-prev-priority";
|
||||
|
||||
/**
|
||||
* Mask the DOM so a single layer screenshot captures ONLY the layer's pixels.
|
||||
@@ -288,6 +291,9 @@ export const DOM_LAYER_MASK_STYLE_ID = "__hf_dom_layer_mask__";
|
||||
* elements that are descendants of a container layer (for example HDR
|
||||
* videos and other-layer SDR videos are descendants of `#root` when we
|
||||
* capture the root DOM layer).
|
||||
* 3. Inline-hide timed descendants of shown elements that were hidden before
|
||||
* the mask was installed. This covers idless child clips and same-layer
|
||||
* descendants that the `extraHideIds` id list cannot represent.
|
||||
*
|
||||
* Only `visibility` is set on extraHideIds — never `opacity`. CSS opacity is
|
||||
* multiplicative through the descendant chain and a descendant cannot escape
|
||||
@@ -315,12 +321,49 @@ export async function applyDomLayerMask(
|
||||
extraHideIds: string[],
|
||||
): Promise<void> {
|
||||
await page.evaluate(
|
||||
(args: { show: string[]; hide: string[]; styleId: string }) => {
|
||||
(args: {
|
||||
show: string[];
|
||||
hide: string[];
|
||||
styleId: string;
|
||||
hiddenAttr: string;
|
||||
prevVisibilityAttr: string;
|
||||
prevPriorityAttr: string;
|
||||
}) => {
|
||||
const existing = document.getElementById(args.styleId);
|
||||
if (existing) existing.remove();
|
||||
|
||||
const restoreMaskedTimedDescendants = () => {
|
||||
const masked = document.querySelectorAll(`[${args.hiddenAttr}="1"]`);
|
||||
for (const node of masked) {
|
||||
if (!(node instanceof HTMLElement)) continue;
|
||||
const prevVisibility = node.getAttribute(args.prevVisibilityAttr);
|
||||
const prevPriority = node.getAttribute(args.prevPriorityAttr);
|
||||
if (prevVisibility === null) {
|
||||
node.style.removeProperty("visibility");
|
||||
} else {
|
||||
node.style.setProperty("visibility", prevVisibility, prevPriority ?? "");
|
||||
}
|
||||
node.removeAttribute(args.hiddenAttr);
|
||||
node.removeAttribute(args.prevVisibilityAttr);
|
||||
node.removeAttribute(args.prevPriorityAttr);
|
||||
}
|
||||
};
|
||||
restoreMaskedTimedDescendants();
|
||||
|
||||
const hiddenTimedDescendants: HTMLElement[] = [];
|
||||
const rememberHiddenTimedDescendants = (root: Element) => {
|
||||
for (const node of root.querySelectorAll("[data-start]")) {
|
||||
if (!(node instanceof HTMLElement)) continue;
|
||||
const computed = window.getComputedStyle(node);
|
||||
if (computed.visibility !== "hidden" && computed.display !== "none") continue;
|
||||
hiddenTimedDescendants.push(node);
|
||||
}
|
||||
};
|
||||
|
||||
const showSelectors: string[] = [];
|
||||
for (const id of args.show) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) rememberHiddenTimedDescendants(el);
|
||||
const escaped = CSS.escape(id);
|
||||
showSelectors.push(`#${escaped}`, `#${escaped} *`);
|
||||
const renderEscaped = CSS.escape(`__render_frame_${id}__`);
|
||||
@@ -338,6 +381,27 @@ export async function applyDomLayerMask(
|
||||
style.textContent = `${massHideRule}\n${showRule}`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
for (const el of hiddenTimedDescendants) {
|
||||
if (el.getAttribute(args.hiddenAttr) === "1") continue;
|
||||
const prevVisibility = el.style.getPropertyValue("visibility");
|
||||
const prevPriority =
|
||||
typeof el.style.getPropertyPriority === "function"
|
||||
? el.style.getPropertyPriority("visibility")
|
||||
: "";
|
||||
if (prevVisibility) {
|
||||
el.setAttribute(args.prevVisibilityAttr, prevVisibility);
|
||||
} else {
|
||||
el.removeAttribute(args.prevVisibilityAttr);
|
||||
}
|
||||
if (prevPriority) {
|
||||
el.setAttribute(args.prevPriorityAttr, prevPriority);
|
||||
} else {
|
||||
el.removeAttribute(args.prevPriorityAttr);
|
||||
}
|
||||
el.setAttribute(args.hiddenAttr, "1");
|
||||
el.style.setProperty("visibility", "hidden", "important");
|
||||
}
|
||||
|
||||
for (const id of args.hide) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
@@ -349,7 +413,14 @@ export async function applyDomLayerMask(
|
||||
}
|
||||
}
|
||||
},
|
||||
{ show: showIds, hide: extraHideIds, styleId: DOM_LAYER_MASK_STYLE_ID },
|
||||
{
|
||||
show: showIds,
|
||||
hide: extraHideIds,
|
||||
styleId: DOM_LAYER_MASK_STYLE_ID,
|
||||
hiddenAttr: DOM_LAYER_MASK_HIDDEN_ATTR,
|
||||
prevVisibilityAttr: DOM_LAYER_MASK_PREV_VISIBILITY_ATTR,
|
||||
prevPriorityAttr: DOM_LAYER_MASK_PREV_PRIORITY_ATTR,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -369,9 +440,29 @@ export async function applyDomLayerMask(
|
||||
*/
|
||||
export async function removeDomLayerMask(page: Page, extraHideIds: string[]): Promise<void> {
|
||||
await page.evaluate(
|
||||
(args: { hide: string[]; styleId: string }) => {
|
||||
(args: {
|
||||
hide: string[];
|
||||
styleId: string;
|
||||
hiddenAttr: string;
|
||||
prevVisibilityAttr: string;
|
||||
prevPriorityAttr: string;
|
||||
}) => {
|
||||
const style = document.getElementById(args.styleId);
|
||||
if (style) style.remove();
|
||||
const masked = document.querySelectorAll(`[${args.hiddenAttr}="1"]`);
|
||||
for (const node of masked) {
|
||||
if (!(node instanceof HTMLElement)) continue;
|
||||
const prevVisibility = node.getAttribute(args.prevVisibilityAttr);
|
||||
const prevPriority = node.getAttribute(args.prevPriorityAttr);
|
||||
if (prevVisibility === null) {
|
||||
node.style.removeProperty("visibility");
|
||||
} else {
|
||||
node.style.setProperty("visibility", prevVisibility, prevPriority ?? "");
|
||||
}
|
||||
node.removeAttribute(args.hiddenAttr);
|
||||
node.removeAttribute(args.prevVisibilityAttr);
|
||||
node.removeAttribute(args.prevPriorityAttr);
|
||||
}
|
||||
for (const id of args.hide) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
@@ -381,7 +472,13 @@ export async function removeDomLayerMask(page: Page, extraHideIds: string[]): Pr
|
||||
if (img) img.style.removeProperty("visibility");
|
||||
}
|
||||
},
|
||||
{ hide: extraHideIds, styleId: DOM_LAYER_MASK_STYLE_ID },
|
||||
{
|
||||
hide: extraHideIds,
|
||||
styleId: DOM_LAYER_MASK_STYLE_ID,
|
||||
hiddenAttr: DOM_LAYER_MASK_HIDDEN_ATTR,
|
||||
prevVisibilityAttr: DOM_LAYER_MASK_PREV_VISIBILITY_ATTR,
|
||||
prevPriorityAttr: DOM_LAYER_MASK_PREV_PRIORITY_ATTR,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -371,6 +371,13 @@ export interface ElementStackingInfo {
|
||||
layoutHeight: number;
|
||||
opacity: number;
|
||||
visible: boolean;
|
||||
/**
|
||||
* True when the SDR video replacement image injected beside this element is
|
||||
* currently paintable. Native videos are hidden during capture, so this lets
|
||||
* the layered HDR compositor keep their replacement frames in the right DOM
|
||||
* layer without reviving unrelated hidden elements.
|
||||
*/
|
||||
renderFrameVisible: boolean;
|
||||
isHdr: boolean;
|
||||
transform: string; // CSS transform matrix string, e.g. "matrix(1,0,0,1,0,0)" or "none"
|
||||
borderRadius: [number, number, number, number]; // [tl, tr, br, bl] in CSS px from nearest clipping ancestor
|
||||
@@ -635,6 +642,17 @@ export async function queryElementStacking(
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
function isElementPaintable(node: Element): boolean {
|
||||
const rect = node.getBoundingClientRect();
|
||||
const style = window.getComputedStyle(node);
|
||||
return (
|
||||
style.visibility !== "hidden" &&
|
||||
style.display !== "none" &&
|
||||
rect.width > 0 &&
|
||||
rect.height > 0
|
||||
);
|
||||
}
|
||||
|
||||
for (const el of elements) {
|
||||
const id = el.id;
|
||||
if (!id) continue;
|
||||
@@ -647,11 +665,9 @@ export async function queryElementStacking(
|
||||
// remains the GSAP-controlled value. Walk from the element itself to
|
||||
// multiply through any ancestor opacity stacks.
|
||||
const opacity = getEffectiveOpacity(el);
|
||||
const visible =
|
||||
style.visibility !== "hidden" &&
|
||||
style.display !== "none" &&
|
||||
rect.width > 0 &&
|
||||
rect.height > 0;
|
||||
const visible = isElementPaintable(el);
|
||||
const renderFrame = document.getElementById(`__render_frame_${id}__`);
|
||||
const renderFrameVisible = renderFrame ? isElementPaintable(renderFrame) : false;
|
||||
// offsetWidth/offsetHeight only exist on HTMLElement (not on
|
||||
// SVGElement, MathMLElement, etc.). Fall back to the bounding rect
|
||||
// dimensions for non-HTML elements so callers always get sensible
|
||||
@@ -668,6 +684,7 @@ export async function queryElementStacking(
|
||||
layoutHeight: htmlEl?.offsetHeight || Math.round(rect.height),
|
||||
opacity,
|
||||
visible,
|
||||
renderFrameVisible,
|
||||
isHdr: hdrSet.has(id),
|
||||
// For HDR elements, use the full accumulated viewport matrix so the
|
||||
// affine blit can apply rotation/scale/translate properly. For DOM
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ElementStackingInfo } from "@hyperframes/engine";
|
||||
import { selectDomLayerShowIds } from "./hdrCompositor.js";
|
||||
|
||||
function makeEl(id: string, overrides?: Partial<ElementStackingInfo>): ElementStackingInfo {
|
||||
return {
|
||||
id,
|
||||
zIndex: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
layoutWidth: 1920,
|
||||
layoutHeight: 1080,
|
||||
opacity: 1,
|
||||
visible: true,
|
||||
renderFrameVisible: false,
|
||||
isHdr: false,
|
||||
transform: "none",
|
||||
borderRadius: [0, 0, 0, 0],
|
||||
objectFit: "fill",
|
||||
objectPosition: "50% 50%",
|
||||
clipRect: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("selectDomLayerShowIds", () => {
|
||||
it("does not re-show hidden DOM elements while preserving visible injected video frames", () => {
|
||||
expect(
|
||||
selectDomLayerShowIds(
|
||||
["visible-overlay", "hidden-later-scene", "hidden-sdr-video"],
|
||||
[
|
||||
makeEl("visible-overlay"),
|
||||
makeEl("hidden-later-scene", { visible: false }),
|
||||
makeEl("hidden-sdr-video", {
|
||||
visible: false,
|
||||
renderFrameVisible: true,
|
||||
}),
|
||||
],
|
||||
),
|
||||
).toEqual(["visible-overlay", "hidden-sdr-video"]);
|
||||
});
|
||||
|
||||
it("does not re-show opacity-zero scene members or their injected frames", () => {
|
||||
expect(
|
||||
selectDomLayerShowIds(
|
||||
["active-overlay", "inactive-scene-video", "inactive-scene-label"],
|
||||
[
|
||||
makeEl("active-overlay"),
|
||||
makeEl("inactive-scene-video", {
|
||||
opacity: 0,
|
||||
visible: false,
|
||||
renderFrameVisible: true,
|
||||
}),
|
||||
makeEl("inactive-scene-label", {
|
||||
opacity: 0,
|
||||
visible: true,
|
||||
}),
|
||||
],
|
||||
),
|
||||
).toEqual(["active-overlay"]);
|
||||
});
|
||||
});
|
||||
@@ -416,6 +416,17 @@ export function resolveCompositeTransfer(
|
||||
return hasHdrContent && effectiveHdr ? effectiveHdr.transfer : "srgb";
|
||||
}
|
||||
|
||||
export function selectDomLayerShowIds(
|
||||
layerElementIds: string[],
|
||||
fullStacking: ElementStackingInfo[],
|
||||
): string[] {
|
||||
const byId = new Map(fullStacking.map((el) => [el.id, el]));
|
||||
return layerElementIds.filter((id) => {
|
||||
const el = byId.get(id);
|
||||
return !!el && el.opacity > 0 && (el.visible || el.renderFrameVisible === true);
|
||||
});
|
||||
}
|
||||
|
||||
export interface HdrCompositeContext {
|
||||
log: ProducerLogger;
|
||||
domSession: CaptureSession;
|
||||
@@ -607,10 +618,10 @@ export async function compositeHdrFrame(
|
||||
//
|
||||
// The mask:
|
||||
// - mass-hides every body descendant via stylesheet
|
||||
// - re-shows the layer's elements (and their descendants and
|
||||
// their injected `__render_frame_*` siblings) so deep-nested
|
||||
// content stays visible even though intermediate ancestors
|
||||
// are hidden
|
||||
// - re-shows the layer's currently paintable elements (and their
|
||||
// descendants and injected `__render_frame_*` siblings) so
|
||||
// deep-nested content stays visible even though intermediate
|
||||
// ancestors are hidden
|
||||
// - inline-hides every other data-start element so they don't
|
||||
// paint when they happen to be descendants of a layer element
|
||||
// (most importantly: HDR videos and other-layer SDR videos
|
||||
@@ -621,6 +632,17 @@ export async function compositeHdrFrame(
|
||||
// border/box-shadow of cards, etc.) and the resulting opaque
|
||||
// pixels overwrite previously composited HDR content beneath.
|
||||
const layerIds = new Set(layer.elementIds);
|
||||
const showIds = selectDomLayerShowIds(layer.elementIds, fullStacking);
|
||||
if (showIds.length === 0) {
|
||||
if (shouldLog) {
|
||||
log.info("[diag] dom layer skipped, all elements not paintable", {
|
||||
frame: debugFrameIndex,
|
||||
layerIdx,
|
||||
layerIds: layer.elementIds,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const hideIds = allElementIds.filter((id) => !layerIds.has(id));
|
||||
if (hdrPerf) hdrPerf.domLayerCaptures += 1;
|
||||
|
||||
@@ -640,7 +662,7 @@ export async function compositeHdrFrame(
|
||||
|
||||
// 3. Install the mask (mass-hide stylesheet + inline-hide non-layer ids)
|
||||
await timeHdrPhaseAsync(hdrPerf, "domMaskApplyMs", () =>
|
||||
applyDomLayerMask(domSession.page, layer.elementIds, hideIds),
|
||||
applyDomLayerMask(domSession.page, showIds, hideIds),
|
||||
);
|
||||
|
||||
// 4. Screenshot
|
||||
@@ -669,6 +691,7 @@ export async function compositeHdrFrame(
|
||||
frame: debugFrameIndex,
|
||||
layerIdx,
|
||||
layerIds: layer.elementIds,
|
||||
showIds,
|
||||
hideCount: hideIds.length,
|
||||
pngBytes: domPng.length,
|
||||
alphaPixels,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ElementStackingInfo } from "@hyperframes/engine";
|
||||
import {
|
||||
applyDomLayerMask,
|
||||
blitRgba8OverRgb48le,
|
||||
captureAlphaPng,
|
||||
decodePng,
|
||||
removeDomLayerMask,
|
||||
} from "@hyperframes/engine";
|
||||
import type { ProducerLogger } from "../../../logger.js";
|
||||
import type { HdrCompositeContext } from "../../hdrCompositor.js";
|
||||
import { captureSceneIntoBuffer } from "./captureHdrFrameShared.js";
|
||||
|
||||
vi.mock("@hyperframes/engine", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@hyperframes/engine")>();
|
||||
return {
|
||||
...actual,
|
||||
applyDomLayerMask: vi.fn(async () => undefined),
|
||||
captureAlphaPng: vi.fn(async () => Buffer.from("png")),
|
||||
decodePng: vi.fn(() => ({ data: Buffer.alloc(4), width: 1, height: 1 })),
|
||||
removeDomLayerMask: vi.fn(async () => undefined),
|
||||
blitRgba8OverRgb48le: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
function makeEl(id: string, overrides?: Partial<ElementStackingInfo>): ElementStackingInfo {
|
||||
return {
|
||||
id,
|
||||
zIndex: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
layoutWidth: 1,
|
||||
layoutHeight: 1,
|
||||
opacity: 1,
|
||||
visible: true,
|
||||
renderFrameVisible: false,
|
||||
isHdr: false,
|
||||
transform: "none",
|
||||
borderRadius: [0, 0, 0, 0],
|
||||
objectFit: "fill",
|
||||
objectPosition: "50% 50%",
|
||||
clipRect: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeContext(): HdrCompositeContext {
|
||||
return {
|
||||
log: makeLogger(),
|
||||
domSession: { page: { evaluate: vi.fn() } } as never,
|
||||
beforeCaptureHook: null,
|
||||
width: 1,
|
||||
height: 1,
|
||||
fps: 30,
|
||||
compositeTransfer: "srgb",
|
||||
nativeHdrImageIds: new Set(),
|
||||
hdrImageBuffers: new Map(),
|
||||
hdrImageTransferCache: new Map(),
|
||||
hdrVideoFrameSources: new Map(),
|
||||
hdrVideoStartTimes: new Map(),
|
||||
imageTransfers: new Map(),
|
||||
videoTransfers: new Map(),
|
||||
debugDumpEnabled: false,
|
||||
debugDumpDir: null,
|
||||
};
|
||||
}
|
||||
|
||||
function makeLogger(): ProducerLogger {
|
||||
return {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
} as unknown as ProducerLogger;
|
||||
}
|
||||
|
||||
describe("captureSceneIntoBuffer", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(applyDomLayerMask).mockClear();
|
||||
vi.mocked(captureAlphaPng).mockClear();
|
||||
vi.mocked(decodePng).mockClear();
|
||||
vi.mocked(removeDomLayerMask).mockClear();
|
||||
vi.mocked(blitRgba8OverRgb48le).mockClear();
|
||||
});
|
||||
|
||||
it("does not re-show hidden transition-scene members in the DOM mask", async () => {
|
||||
const page = { evaluate: vi.fn(async () => undefined) };
|
||||
|
||||
await captureSceneIntoBuffer({
|
||||
session: { page, onBeforeCapture: null } as never,
|
||||
sceneBuf: Buffer.alloc(6),
|
||||
sceneIds: new Set(["visible-overlay", "hidden-inner", "hidden-sdr-video"]),
|
||||
stackingInfo: [
|
||||
makeEl("visible-overlay"),
|
||||
makeEl("hidden-inner", { visible: false }),
|
||||
makeEl("hidden-sdr-video", { visible: false, renderFrameVisible: true }),
|
||||
makeEl("outside-scene"),
|
||||
],
|
||||
time: 0.5,
|
||||
width: 1,
|
||||
height: 1,
|
||||
nativeHdrIds: new Set(),
|
||||
nativeHdrImageIds: new Set(),
|
||||
beforeCaptureHook: null,
|
||||
hdrCompositeCtx: makeContext(),
|
||||
compositeTransfer: "srgb",
|
||||
hdrTargetTransfer: undefined,
|
||||
hdrPerf: undefined,
|
||||
log: makeLogger(),
|
||||
frameIdx: 15,
|
||||
});
|
||||
|
||||
expect(applyDomLayerMask).toHaveBeenCalledWith(
|
||||
page,
|
||||
["visible-overlay", "hidden-sdr-video"],
|
||||
["outside-scene"],
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
blitHdrImageLayer,
|
||||
blitHdrVideoLayer,
|
||||
closeHdrVideoFrameSource,
|
||||
selectDomLayerShowIds,
|
||||
} from "../../hdrCompositor.js";
|
||||
import {
|
||||
type HdrPerfCollector,
|
||||
@@ -234,10 +235,11 @@ export async function captureSceneIntoBuffer(a: CaptureSceneArgs): Promise<void>
|
||||
);
|
||||
}
|
||||
}
|
||||
const showIds = Array.from(sceneIds);
|
||||
const showIds = selectDomLayerShowIds(Array.from(sceneIds), stackingInfo);
|
||||
const hideIds = stackingInfo
|
||||
.map((e) => e.id)
|
||||
.filter((id) => !sceneIds.has(id) || nativeHdrIds.has(id));
|
||||
if (showIds.length === 0) return;
|
||||
if (hdrPerf) hdrPerf.domLayerCaptures += 1;
|
||||
await timeHdrPhaseAsync(hdrPerf, "domMaskApplyMs", () =>
|
||||
applyDomLayerMask(session.page, showIds, hideIds),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:69c2f759a418a6e85094744011f5c267a2f311527de880b8d0ceb7cd46279c15
|
||||
size 1793594
|
||||
oid sha256:5808c1d2d8e794568deb36ff8c727066d3c6846bd764f4dc10b154c8c329e717
|
||||
size 3689447
|
||||
|
||||
Reference in New Issue
Block a user