mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
641d934337 |
@@ -298,6 +298,34 @@ describe("renderVom single-layer page", () => {
|
|||||||
expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 4 }]);
|
expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 4 }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders through deeply nested transparent wrappers without exhausting the call stack", () => {
|
||||||
|
const wrapperCount = 10_000;
|
||||||
|
const nodes = [node({ id: 1, role: "RootWebArea", name: "Doc" })];
|
||||||
|
for (let index = 0; index < wrapperCount; index += 1) {
|
||||||
|
nodes.push(
|
||||||
|
node({
|
||||||
|
id: index + 2,
|
||||||
|
parentId: index + 1,
|
||||||
|
role: "generic",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
nodes.push(
|
||||||
|
node({
|
||||||
|
id: wrapperCount + 2,
|
||||||
|
parentId: wrapperCount + 1,
|
||||||
|
role: "button",
|
||||||
|
name: "Deep action",
|
||||||
|
tag: "button",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const out = renderVom(scene(nodes));
|
||||||
|
|
||||||
|
expect(out.text).toContain('@e1 button "Deep action"');
|
||||||
|
expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: wrapperCount + 2 }]);
|
||||||
|
});
|
||||||
|
|
||||||
it("truncates when maxTokens is exceeded while keeping refs in sync with rendered text", () => {
|
it("truncates when maxTokens is exceeded while keeping refs in sync with rendered text", () => {
|
||||||
const out = renderVom(
|
const out = renderVom(
|
||||||
scene([
|
scene([
|
||||||
@@ -785,6 +813,55 @@ describe("renderVom single-layer page", () => {
|
|||||||
expect(out.text).not.toContain("北京市小客车指标调控管理信息系统]");
|
expect(out.text).not.toContain("北京市小客车指标调控管理信息系统]");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("derives duplicate-label context from indexed DOM ancestry without semantic proximity", () => {
|
||||||
|
const out = renderVom(
|
||||||
|
scene([
|
||||||
|
node({ id: 1, role: "RootWebArea", name: "Doc", contextScopeId: "root" }),
|
||||||
|
node({
|
||||||
|
id: 3,
|
||||||
|
parentId: 1,
|
||||||
|
role: "button",
|
||||||
|
name: "View",
|
||||||
|
tag: "button",
|
||||||
|
domParentId: 2,
|
||||||
|
domAncestorIds: [2, 1],
|
||||||
|
contextScopeId: "root",
|
||||||
|
}),
|
||||||
|
node({
|
||||||
|
id: 5,
|
||||||
|
parentId: 1,
|
||||||
|
role: "button",
|
||||||
|
name: "View",
|
||||||
|
tag: "button",
|
||||||
|
domParentId: 4,
|
||||||
|
domAncestorIds: [4, 1],
|
||||||
|
contextScopeId: "root",
|
||||||
|
}),
|
||||||
|
node({
|
||||||
|
id: 2,
|
||||||
|
parentId: 1,
|
||||||
|
role: "group",
|
||||||
|
name: "Project Alpha",
|
||||||
|
domParentId: 1,
|
||||||
|
domAncestorIds: [1],
|
||||||
|
contextScopeId: "root",
|
||||||
|
}),
|
||||||
|
node({
|
||||||
|
id: 4,
|
||||||
|
parentId: 1,
|
||||||
|
role: "group",
|
||||||
|
name: "Project Beta",
|
||||||
|
domParentId: 1,
|
||||||
|
domAncestorIds: [1],
|
||||||
|
contextScopeId: "root",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(out.text).toContain('@e1 button "View" [ctx: Project Alpha]');
|
||||||
|
expect(out.text).toContain('@e2 button "View" [ctx: Project Beta]');
|
||||||
|
});
|
||||||
|
|
||||||
it("does not add low-value root context to duplicated brand links", () => {
|
it("does not add low-value root context to duplicated brand links", () => {
|
||||||
const out = renderVom(
|
const out = renderVom(
|
||||||
scene([
|
scene([
|
||||||
@@ -833,6 +910,34 @@ describe("renderVom single-layer page", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("renderVom double-layer page", () => {
|
describe("renderVom double-layer page", () => {
|
||||||
|
it("collects blocking-layer descendants even when children precede parents in source order", () => {
|
||||||
|
const out = renderVom(
|
||||||
|
scene([
|
||||||
|
node({ id: 1, role: "RootWebArea", name: "Doc" }),
|
||||||
|
node({
|
||||||
|
id: 3,
|
||||||
|
parentId: 2,
|
||||||
|
role: "button",
|
||||||
|
name: "Close",
|
||||||
|
tag: "button",
|
||||||
|
paintOrder: 0,
|
||||||
|
}),
|
||||||
|
node({
|
||||||
|
id: 2,
|
||||||
|
parentId: 1,
|
||||||
|
role: "dialog",
|
||||||
|
name: "Modal",
|
||||||
|
rect: { x: 0, y: 0, w: 1280, h: 720 },
|
||||||
|
paintOrder: 10,
|
||||||
|
position: "fixed",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(out.text).toContain('@e1 button "Close"');
|
||||||
|
expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 3 }]);
|
||||||
|
});
|
||||||
|
|
||||||
it("renders a blocking modal first and folds the base page into an occluded summary", () => {
|
it("renders a blocking modal first and folds the base page into an occluded summary", () => {
|
||||||
const out = renderVom(
|
const out = renderVom(
|
||||||
scene([
|
scene([
|
||||||
|
|||||||
+134
-66
@@ -421,6 +421,41 @@ function buildChildren(nodes: VomNode[]): Map<number | null, VomNode[]> {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CONTEXT_LABEL_ROLES = new Set([
|
||||||
|
"heading",
|
||||||
|
"article",
|
||||||
|
"section",
|
||||||
|
"group",
|
||||||
|
"region",
|
||||||
|
"row",
|
||||||
|
"form",
|
||||||
|
]);
|
||||||
|
|
||||||
|
interface DomContextIndex {
|
||||||
|
nonBoundaryByScopeParent: Map<string | undefined, Map<number, VomNode[]>>;
|
||||||
|
sourceOrderById: Map<number, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDomContextIndex(nodes: VomNode[]): DomContextIndex {
|
||||||
|
const nonBoundaryByScopeParent = new Map<string | undefined, Map<number, VomNode[]>>();
|
||||||
|
const sourceOrderById = new Map<number, number>();
|
||||||
|
for (const [sourceOrder, node] of nodes.entries()) {
|
||||||
|
sourceOrderById.set(node.id, sourceOrder);
|
||||||
|
if (!CONTEXT_LABEL_ROLES.has(normalizedRole(node)) || isContextBoundary(node)) continue;
|
||||||
|
const parentId = node.domParentId;
|
||||||
|
if (parentId === undefined || parentId === null) continue;
|
||||||
|
let byParent = nonBoundaryByScopeParent.get(node.contextScopeId);
|
||||||
|
if (!byParent) {
|
||||||
|
byParent = new Map();
|
||||||
|
nonBoundaryByScopeParent.set(node.contextScopeId, byParent);
|
||||||
|
}
|
||||||
|
const siblings = byParent.get(parentId) ?? [];
|
||||||
|
siblings.push(node);
|
||||||
|
byParent.set(parentId, siblings);
|
||||||
|
}
|
||||||
|
return { nonBoundaryByScopeParent, sourceOrderById };
|
||||||
|
}
|
||||||
|
|
||||||
function duplicateReferenceNames(nodes: VomNode[]): Set<string> {
|
function duplicateReferenceNames(nodes: VomNode[]): Set<string> {
|
||||||
const counts = new Map<string, number>();
|
const counts = new Map<string, number>();
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
@@ -440,13 +475,7 @@ function needsHandleContext(nodeName: string, duplicateRefNames: Set<string>): b
|
|||||||
}
|
}
|
||||||
|
|
||||||
function contextLabel(node: VomNode, targetName: string): string | undefined {
|
function contextLabel(node: VomNode, targetName: string): string | undefined {
|
||||||
if (
|
if (!CONTEXT_LABEL_ROLES.has(normalizedRole(node))) return undefined;
|
||||||
!["heading", "article", "section", "group", "region", "row", "form"].includes(
|
|
||||||
normalizedRole(node),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return weakContextText(
|
return weakContextText(
|
||||||
cleaned(node.name) ?? cleaned(node.text) ?? cleaned(node.nearbyText),
|
cleaned(node.name) ?? cleaned(node.text) ?? cleaned(node.nearbyText),
|
||||||
targetName,
|
targetName,
|
||||||
@@ -607,19 +636,45 @@ function collectDomContext(
|
|||||||
): ContextCandidate[] {
|
): ContextCandidate[] {
|
||||||
const ancestorIds = node.domAncestorIds ?? [];
|
const ancestorIds = node.domAncestorIds ?? [];
|
||||||
if (ancestorIds.length === 0) return [];
|
if (ancestorIds.length === 0) return [];
|
||||||
const ancestors = new Set(ancestorIds);
|
|
||||||
const contexts: ContextCandidate[] = [];
|
const contexts: ContextCandidate[] = [];
|
||||||
|
const ancestorRank = new Map<number, number>();
|
||||||
|
for (const [rank, ancestorId] of ancestorIds.entries()) {
|
||||||
|
if (!ancestorRank.has(ancestorId)) ancestorRank.set(ancestorId, rank);
|
||||||
|
}
|
||||||
|
|
||||||
for (const candidate of state.nodesById.values()) {
|
const candidates = new Map<number, VomNode>();
|
||||||
|
const nonBoundaryByParent = state.domContextIndex.nonBoundaryByScopeParent.get(
|
||||||
|
node.contextScopeId,
|
||||||
|
);
|
||||||
|
for (const ancestorId of ancestorRank.keys()) {
|
||||||
|
const boundary = state.nodesById.get(ancestorId);
|
||||||
|
const boundaryParentId = boundary?.domParentId;
|
||||||
|
if (
|
||||||
|
boundary &&
|
||||||
|
sharesContextScope(node, boundary) &&
|
||||||
|
isContextBoundary(boundary) &&
|
||||||
|
boundaryParentId !== undefined &&
|
||||||
|
boundaryParentId !== null &&
|
||||||
|
ancestorRank.has(boundaryParentId)
|
||||||
|
) {
|
||||||
|
candidates.set(boundary.id, boundary);
|
||||||
|
}
|
||||||
|
for (const candidate of nonBoundaryByParent?.get(ancestorId) ?? []) {
|
||||||
|
candidates.set(candidate.id, candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceOrderById = state.domContextIndex.sourceOrderById;
|
||||||
|
const orderedCandidates = [...candidates.values()].sort(
|
||||||
|
(a, b) => (sourceOrderById.get(a.id) ?? 0) - (sourceOrderById.get(b.id) ?? 0),
|
||||||
|
);
|
||||||
|
for (const candidate of orderedCandidates) {
|
||||||
if (candidate.id === node.id) continue;
|
if (candidate.id === node.id) continue;
|
||||||
if (!sharesContextScope(node, candidate)) continue;
|
|
||||||
if (isContextBoundary(candidate) && !ancestorIds.includes(candidate.id)) continue;
|
|
||||||
const parentId = candidate.domParentId;
|
const parentId = candidate.domParentId;
|
||||||
if (parentId === undefined || parentId === null || !ancestors.has(parentId)) continue;
|
if (parentId === undefined || parentId === null) continue;
|
||||||
const label = contextLabel(candidate, nodeName);
|
const label = contextLabel(candidate, nodeName);
|
||||||
if (!label) continue;
|
if (!label) continue;
|
||||||
const rank = ancestorIds.indexOf(parentId);
|
contexts.push({ text: label, source: "dom", order: ancestorRank.get(parentId) ?? 0 });
|
||||||
contexts.push({ text: label, source: "dom", order: rank < 0 ? contexts.length : rank });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return contexts;
|
return contexts;
|
||||||
@@ -734,6 +789,7 @@ interface RenderState {
|
|||||||
children: Map<number | null, VomNode[]>;
|
children: Map<number | null, VomNode[]>;
|
||||||
parentMap: Map<number, number | null>;
|
parentMap: Map<number, number | null>;
|
||||||
nodesById: Map<number, VomNode>;
|
nodesById: Map<number, VomNode>;
|
||||||
|
domContextIndex: DomContextIndex;
|
||||||
duplicateRefNames: Set<string>;
|
duplicateRefNames: Set<string>;
|
||||||
surfaceMap: Map<number, CondSurface>;
|
surfaceMap: Map<number, CondSurface>;
|
||||||
scopeMap: Map<number, ActiveScopeBlock>;
|
scopeMap: Map<number, ActiveScopeBlock>;
|
||||||
@@ -768,20 +824,25 @@ function emitActiveScopeBlock(scope: ActiveScopeBlock, depth: number, state: Ren
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTree(
|
function pushRenderChildren(
|
||||||
children: Map<number | null, VomNode[]>,
|
stack: Array<{ node: VomNode; depth: number }>,
|
||||||
parentId: number | null,
|
children: readonly VomNode[],
|
||||||
depth: number,
|
depth: number,
|
||||||
state: RenderState,
|
|
||||||
): void {
|
): void {
|
||||||
if (state.stopped) return;
|
for (let index = children.length - 1; index >= 0; index -= 1) {
|
||||||
|
stack.push({ node: children[index], depth });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const nodes = children.get(parentId) ?? [];
|
function renderTree(children: Map<number | null, VomNode[]>, state: RenderState): void {
|
||||||
for (const node of nodes) {
|
const stack: Array<{ node: VomNode; depth: number }> = [];
|
||||||
if (state.stopped) return;
|
pushRenderChildren(stack, children.get(null) ?? [], 1);
|
||||||
|
|
||||||
|
while (stack.length > 0 && !state.stopped) {
|
||||||
|
const { node, depth } = stack.pop() as { node: VomNode; depth: number };
|
||||||
|
|
||||||
if (!shouldRender(node)) {
|
if (!shouldRender(node)) {
|
||||||
renderTree(children, node.id, depth, state);
|
pushRenderChildren(stack, children.get(node.id) ?? [], depth);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,7 +859,7 @@ function renderTree(
|
|||||||
if (nextTokens > state.maxTokens) {
|
if (nextTokens > state.maxTokens) {
|
||||||
state.truncated = true;
|
state.truncated = true;
|
||||||
state.stopped = true;
|
state.stopped = true;
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.lines.push(line);
|
state.lines.push(line);
|
||||||
@@ -823,7 +884,7 @@ function renderTree(
|
|||||||
if ((ref && shouldSkipRedundantRefChildren(node)) || shouldSkipRedundantChildren(node, state)) {
|
if ((ref && shouldSkipRedundantRefChildren(node)) || shouldSkipRedundantChildren(node, state)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
renderTree(children, node.id, depth + 1, state);
|
pushRenderChildren(stack, children.get(node.id) ?? [], depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,12 +909,13 @@ function renderNodes(
|
|||||||
children,
|
children,
|
||||||
parentMap: buildParentMap(nodes),
|
parentMap: buildParentMap(nodes),
|
||||||
nodesById: new Map(nodes.map((node) => [node.id, node])),
|
nodesById: new Map(nodes.map((node) => [node.id, node])),
|
||||||
|
domContextIndex: buildDomContextIndex(nodes),
|
||||||
duplicateRefNames: duplicateReferenceNames(nodes),
|
duplicateRefNames: duplicateReferenceNames(nodes),
|
||||||
surfaceMap: new Map(surfaces.map((surface) => [surface.triggerId, surface])),
|
surfaceMap: new Map(surfaces.map((surface) => [surface.triggerId, surface])),
|
||||||
scopeMap: new Map(activeScopeBlocks.map((scope) => [scope.triggerId, scope])),
|
scopeMap: new Map(activeScopeBlocks.map((scope) => [scope.triggerId, scope])),
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTree(children, null, 1, state);
|
renderTree(children, state);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -922,23 +984,33 @@ function paintLineageByFrame(
|
|||||||
return lineage;
|
return lineage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cachedPaintLineageByFrame(
|
||||||
|
node: VomNode,
|
||||||
|
parentMap: Map<number, number | null>,
|
||||||
|
nodesById: Map<number, VomNode>,
|
||||||
|
cache: Map<number, Map<string | undefined, VomNode>>,
|
||||||
|
): Map<string | undefined, VomNode> {
|
||||||
|
const cached = cache.get(node.id);
|
||||||
|
if (cached) return cached;
|
||||||
|
const lineage = paintLineageByFrame(node, parentMap, nodesById);
|
||||||
|
cache.set(node.id, lineage);
|
||||||
|
return lineage;
|
||||||
|
}
|
||||||
|
|
||||||
/** Resolve both nodes to the nearest document where their paint order is comparable. */
|
/** Resolve both nodes to the nearest document where their paint order is comparable. */
|
||||||
function comparablePaintNodes(
|
function comparablePaintNodes(
|
||||||
target: VomNode,
|
target: VomNode,
|
||||||
blocker: VomNode,
|
blocker: VomNode,
|
||||||
parentMap: Map<number, number | null>,
|
parentMap: Map<number, number | null>,
|
||||||
nodesById: Map<number, VomNode>,
|
nodesById: Map<number, VomNode>,
|
||||||
|
lineageCache: Map<number, Map<string | undefined, VomNode>>,
|
||||||
): { target: VomNode; blocker: VomNode } | null {
|
): { target: VomNode; blocker: VomNode } | null {
|
||||||
if (target.frameId === blocker.frameId) return { target, blocker };
|
if (target.frameId === blocker.frameId) return { target, blocker };
|
||||||
const targetLineage = paintLineageByFrame(target, parentMap, nodesById);
|
const targetLineage = cachedPaintLineageByFrame(target, parentMap, nodesById, lineageCache);
|
||||||
let blockerInFrame: VomNode | undefined = blocker;
|
const blockerLineage = cachedPaintLineageByFrame(blocker, parentMap, nodesById, lineageCache);
|
||||||
let guard = 0;
|
for (const [frameId, blockerInFrame] of blockerLineage) {
|
||||||
while (blockerInFrame && guard <= parentMap.size) {
|
const targetInFrame = targetLineage.get(frameId);
|
||||||
const targetInFrame = targetLineage.get(blockerInFrame.frameId);
|
|
||||||
if (targetInFrame) return { target: targetInFrame, blocker: blockerInFrame };
|
if (targetInFrame) return { target: targetInFrame, blocker: blockerInFrame };
|
||||||
const parentId: number | null = parentMap.get(blockerInFrame.id) ?? null;
|
|
||||||
blockerInFrame = parentId === null ? undefined : nodesById.get(parentId);
|
|
||||||
guard += 1;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -949,12 +1021,13 @@ function isBlockedByRegion(
|
|||||||
parentMap: Map<number, number | null>,
|
parentMap: Map<number, number | null>,
|
||||||
nodesById: Map<number, VomNode>,
|
nodesById: Map<number, VomNode>,
|
||||||
viewportCoverage: number,
|
viewportCoverage: number,
|
||||||
|
lineageCache: Map<number, Map<string | undefined, VomNode>>,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (target.id === blocker.id) return false;
|
if (target.id === blocker.id) return false;
|
||||||
if (isAncestorOf(parentMap, blocker.id, target.id)) return false;
|
if (isAncestorOf(parentMap, blocker.id, target.id)) return false;
|
||||||
if (isAncestorOf(parentMap, target.id, blocker.id)) return false;
|
if (isAncestorOf(parentMap, target.id, blocker.id)) return false;
|
||||||
if (!target.rect || !blocker.rect) return false;
|
if (!target.rect || !blocker.rect) return false;
|
||||||
const paintNodes = comparablePaintNodes(target, blocker, parentMap, nodesById);
|
const paintNodes = comparablePaintNodes(target, blocker, parentMap, nodesById, lineageCache);
|
||||||
if (!paintNodes) return false;
|
if (!paintNodes) return false;
|
||||||
|
|
||||||
const points = interactionPoints(target.rect);
|
const points = interactionPoints(target.rect);
|
||||||
@@ -979,17 +1052,22 @@ function isBlockedByRegion(
|
|||||||
return points.some(([x, y]) => rectContains(blocker.rect as Rect, x, y));
|
return points.some(([x, y]) => rectContains(blocker.rect as Rect, x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectDescendantsOfIds(nodes: VomNode[], roots: Set<number>): Set<number> {
|
function collectDescendants(nodes: VomNode[], roots: Set<number>): Set<number> {
|
||||||
|
const children = new Map<number, number[]>();
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.parentId === null) continue;
|
||||||
|
const siblings = children.get(node.parentId) ?? [];
|
||||||
|
siblings.push(node.id);
|
||||||
|
children.set(node.parentId, siblings);
|
||||||
|
}
|
||||||
|
|
||||||
const included = new Set(roots);
|
const included = new Set(roots);
|
||||||
let changed = true;
|
const queue = [...roots];
|
||||||
while (changed) {
|
for (let index = 0; index < queue.length; index += 1) {
|
||||||
changed = false;
|
for (const childId of children.get(queue[index]) ?? []) {
|
||||||
for (const node of nodes) {
|
if (included.has(childId)) continue;
|
||||||
if (included.has(node.id)) continue;
|
included.add(childId);
|
||||||
if (node.parentId !== null && included.has(node.parentId)) {
|
queue.push(childId);
|
||||||
included.add(node.id);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return included;
|
return included;
|
||||||
@@ -998,6 +1076,7 @@ function collectDescendantsOfIds(nodes: VomNode[], roots: Set<number>): Set<numb
|
|||||||
function applyActiveRegionPolicy(nodes: VomNode[], scene: VomScene): VomNode[] {
|
function applyActiveRegionPolicy(nodes: VomNode[], scene: VomScene): VomNode[] {
|
||||||
const parentMap = buildParentMap(nodes);
|
const parentMap = buildParentMap(nodes);
|
||||||
const nodesById = new Map(nodes.map((node) => [node.id, node]));
|
const nodesById = new Map(nodes.map((node) => [node.id, node]));
|
||||||
|
const lineageCache = new Map<number, Map<string | undefined, VomNode>>();
|
||||||
const candidates = nodes
|
const candidates = nodes
|
||||||
.filter(isPositionedRegionCandidate)
|
.filter(isPositionedRegionCandidate)
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
@@ -1014,40 +1093,29 @@ function applyActiveRegionPolicy(nodes: VomNode[], scene: VomScene): VomNode[] {
|
|||||||
for (const target of nodes) {
|
for (const target of nodes) {
|
||||||
if (!isVomReferenceNode(target)) continue;
|
if (!isVomReferenceNode(target)) continue;
|
||||||
const blocked = candidates.some((candidate) =>
|
const blocked = candidates.some((candidate) =>
|
||||||
isBlockedByRegion(target, candidate.node, parentMap, nodesById, candidate.viewportCoverage),
|
isBlockedByRegion(
|
||||||
|
target,
|
||||||
|
candidate.node,
|
||||||
|
parentMap,
|
||||||
|
nodesById,
|
||||||
|
candidate.viewportCoverage,
|
||||||
|
lineageCache,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (blocked) blockedRoots.add(target.id);
|
if (blocked) blockedRoots.add(target.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockedRoots.size === 0) return nodes;
|
if (blockedRoots.size === 0) return nodes;
|
||||||
const blocked = collectDescendantsOfIds(nodes, blockedRoots);
|
const blocked = collectDescendants(nodes, blockedRoots);
|
||||||
return nodes.filter((node) => !blocked.has(node.id));
|
return nodes.filter((node) => !blocked.has(node.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectDescendantsOfMembers(nodes: VomNode[], members: Set<number>): Set<number> {
|
|
||||||
const included = new Set(members);
|
|
||||||
let changed = true;
|
|
||||||
|
|
||||||
while (changed) {
|
|
||||||
changed = false;
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (included.has(node.id)) continue;
|
|
||||||
if (node.parentId !== null && included.has(node.parentId)) {
|
|
||||||
included.add(node.id);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return included;
|
|
||||||
}
|
|
||||||
|
|
||||||
function countRenderable(nodes: VomNode[]): number {
|
function countRenderable(nodes: VomNode[]): number {
|
||||||
return nodes.filter(shouldRender).length;
|
return nodes.filter(shouldRender).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDoubleLayer(scene: VomScene, layer: BlockingLayer, options: VomOptions): VomResult {
|
function renderDoubleLayer(scene: VomScene, layer: BlockingLayer, options: VomOptions): VomResult {
|
||||||
const included = collectDescendantsOfMembers(scene.nodes, layer.members);
|
const included = collectDescendants(scene.nodes, layer.members);
|
||||||
const visibleNodes = scene.nodes.filter((node) => included.has(node.id));
|
const visibleNodes = scene.nodes.filter((node) => included.has(node.id));
|
||||||
const hiddenCount = countRenderable(scene.nodes.filter((node) => !included.has(node.id)));
|
const hiddenCount = countRenderable(scene.nodes.filter((node) => !included.has(node.id)));
|
||||||
const header = [
|
const header = [
|
||||||
|
|||||||
Reference in New Issue
Block a user