mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(studio-server): an edit aimed at a clip never lands on an audio group sharing its id (#5645)
* fix(studio-server): an edit aimed at a clip never lands on an audio group sharing its id * refactor(studio-server): one comment line, the rest in names
This commit is contained in:
@@ -603,6 +603,18 @@ describe("T7 — data-hf-id targeting (spec for R1)", () => {
|
||||
expect(html).toMatch(/color:\s*blue/);
|
||||
});
|
||||
|
||||
it("never falls back by id onto an element pinned to another hfId", () => {
|
||||
// A clip in a sub-composition shares the audio group's id; its edit must not land on the group.
|
||||
const source = `<div data-composition-id="main"><hf-audio-group id="voice" data-hf-id="hf-oova" data-fx-chain="[]"></hf-audio-group><div class="clip" data-start="0"></div></div>`;
|
||||
const target = { id: "voice", hfId: "hf-6egx" };
|
||||
expect(removeElementFromHtml(source, target)).toBe(source);
|
||||
const { html, matched } = patchElementInHtml(source, target, [
|
||||
{ type: "attribute", property: "data-start", value: "3" },
|
||||
]);
|
||||
expect(matched).toBe(false);
|
||||
expect(html).toBe(source);
|
||||
});
|
||||
|
||||
it("does not break out of the selector on a crafted hfId (CSS injection guard)", () => {
|
||||
// A value with a quote/bracket must be escaped, not injected — it should
|
||||
// simply match nothing and leave the source untouched, never throw.
|
||||
|
||||
@@ -23,19 +23,22 @@ export interface SourceMutationTarget {
|
||||
selectorIndex?: number;
|
||||
}
|
||||
|
||||
const pinnedInSource = new WeakMap<Document, ReadonlySet<string>>();
|
||||
|
||||
export function parseSourceDocument(raw: string): {
|
||||
document: Document;
|
||||
wrappedFragment: boolean;
|
||||
} {
|
||||
const source = ensureHfIds(raw);
|
||||
const hasDocumentShell = /<!doctype|<html[\s>]/i.test(source);
|
||||
if (hasDocumentShell) {
|
||||
return { document: parseHTML(source).document, wrappedFragment: false };
|
||||
}
|
||||
return {
|
||||
document: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document,
|
||||
wrappedFragment: true,
|
||||
};
|
||||
const document = hasDocumentShell
|
||||
? parseHTML(source).document
|
||||
: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document;
|
||||
pinnedInSource.set(
|
||||
document,
|
||||
new Set(Array.from(raw.matchAll(/\bdata-hf-id\s*=\s*["']?([^"'\s>]+)/gi), (m) => m[1]!)),
|
||||
);
|
||||
return { document, wrappedFragment: !hasDocumentShell };
|
||||
}
|
||||
|
||||
function duplicateCssRulesForId(document: Document, originalId: string, newId: string): void {
|
||||
@@ -123,16 +126,19 @@ export function findTargetElement(
|
||||
const el = findByHfId(document, target.hfId);
|
||||
if (el) return el;
|
||||
}
|
||||
const pinned = pinnedInSource.get(document);
|
||||
const unlessPinnedElsewhere = (el: Element | null | undefined) =>
|
||||
el && !(target.hfId && pinned?.has(el.getAttribute("data-hf-id") ?? "")) ? el : null;
|
||||
|
||||
if (target.id) {
|
||||
const byId = document.getElementById(target.id);
|
||||
const byId = unlessPinnedElsewhere(document.getElementById(target.id));
|
||||
if (byId) return byId;
|
||||
}
|
||||
|
||||
if (!target.selector) return null;
|
||||
try {
|
||||
const matches = querySelectorAllWithTemplates(document, target.selector);
|
||||
return matches[target.selectorIndex ?? 0] ?? null;
|
||||
return unlessPinnedElsewhere(matches[target.selectorIndex ?? 0]);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user