@@ -16,6 +16,47 @@ export type { IframeErrorMessage } from './iframe-errors';
1616const BASELINE_STYLE =
1717 '<style>html,body{margin:0;padding:0;background:var(--color-artifact-bg, #ffffff);min-height:100%;}</style>' ;
1818
19+ const HTML_RE = / < h t m l [ ^ > ] * > / i;
20+ const HEAD_OPEN_RE = / < h e a d [ ^ > ] * > / i;
21+ const HEAD_CLOSE_RE = / < \/ h e a d \s * > / i;
22+ const BODY_OPEN_RE = / < b o d y [ ^ > ] * > / i;
23+ const BODY_CLOSE_RE = / < \/ b o d y \s * > / i;
24+
25+ /**
26+ * Ensure the document has matching <body>...</body> tags so downstream
27+ * baseline/overlay injection can rely on them. Handles all four input
28+ * shapes: both tags, opener only, closer only, neither.
29+ */
30+ function normalizeBodyTags ( html : string ) : string {
31+ const hasOpen = BODY_OPEN_RE . test ( html ) ;
32+ const hasClose = BODY_CLOSE_RE . test ( html ) ;
33+
34+ if ( hasOpen && hasClose ) return html ;
35+
36+ if ( hasOpen && ! hasClose ) {
37+ return `${ html } </body>` ;
38+ }
39+
40+ if ( ! hasOpen && hasClose ) {
41+ if ( HEAD_CLOSE_RE . test ( html ) ) {
42+ return html . replace ( HEAD_CLOSE_RE , ( m ) => `${ m } <body>` ) ;
43+ }
44+ if ( HTML_RE . test ( html ) ) {
45+ return html . replace ( HTML_RE , ( m ) => `${ m } <body>` ) ;
46+ }
47+ return `<body>${ html } ` ;
48+ }
49+
50+ // Neither opener nor closer.
51+ if ( HEAD_CLOSE_RE . test ( html ) ) {
52+ return `${ html . replace ( HEAD_CLOSE_RE , ( m ) => `${ m } <body>` ) } </body>` ;
53+ }
54+ if ( HTML_RE . test ( html ) ) {
55+ return `${ html . replace ( HTML_RE , ( m ) => `${ m } <body>` ) } </body>` ;
56+ }
57+ return `<body>${ html } </body>` ;
58+ }
59+
1960/**
2061 * Build a complete srcdoc HTML string for the preview iframe.
2162 * Strips CSP <meta> tags from user content to allow overlay injection.
@@ -29,30 +70,14 @@ export function buildSrcdoc(userHtml: string): string {
2970 '' ,
3071 ) ;
3172
32- if ( / < \/ b o d y \s * > / i. test ( stripped ) ) {
33- let withBaseline : string ;
34- if ( / < h e a d [ ^ > ] * > / i. test ( stripped ) ) {
35- withBaseline = stripped . replace ( / < h e a d [ ^ > ] * > / i, ( match ) => `${ match } ${ BASELINE_STYLE } ` ) ;
36- } else if ( / < h t m l [ ^ > ] * > / i. test ( stripped ) ) {
37- withBaseline = stripped . replace (
38- / < h t m l [ ^ > ] * > / i,
39- ( match ) => `${ match } <head>${ BASELINE_STYLE } </head>` ,
40- ) ;
41- } else if ( / < b o d y [ ^ > ] * > / i. test ( stripped ) ) {
42- withBaseline = `${ stripped . replace (
43- / < b o d y [ ^ > ] * > / i,
44- ( match ) => `<html><head>${ BASELINE_STYLE } </head>${ match } ` ,
45- ) } </html>`;
46- } else {
47- withBaseline = `<!doctype html><html><head>${ BASELINE_STYLE } </head><body>${ stripped } </body></html>` ;
48- }
49- return withBaseline . replace (
50- / < \/ b o d y \s * > (? ! [ \s \S ] * < \/ b o d y \s * > ) / i,
51- `<script>${ OVERLAY_SCRIPT } </script></body>` ,
52- ) ;
53- }
73+ const hasAnyStructure =
74+ HTML_RE . test ( stripped ) ||
75+ HEAD_OPEN_RE . test ( stripped ) ||
76+ BODY_OPEN_RE . test ( stripped ) ||
77+ BODY_CLOSE_RE . test ( stripped ) ;
5478
55- return `<!doctype html>
79+ if ( ! hasAnyStructure ) {
80+ return `<!doctype html>
5681<html lang="en">
5782<head>
5883<meta charset="utf-8" />
@@ -65,6 +90,26 @@ ${stripped}
6590<script>${ OVERLAY_SCRIPT } </script>
6691</body>
6792</html>` ;
93+ }
94+
95+ const normalized = normalizeBodyTags ( stripped ) ;
96+
97+ let withBaseline : string ;
98+ if ( HEAD_OPEN_RE . test ( normalized ) ) {
99+ withBaseline = normalized . replace ( HEAD_OPEN_RE , ( match ) => `${ match } ${ BASELINE_STYLE } ` ) ;
100+ } else if ( HTML_RE . test ( normalized ) ) {
101+ withBaseline = normalized . replace (
102+ HTML_RE ,
103+ ( match ) => `${ match } <head>${ BASELINE_STYLE } </head>` ,
104+ ) ;
105+ } else {
106+ withBaseline = `<html><head>${ BASELINE_STYLE } </head>${ normalized } </html>` ;
107+ }
108+
109+ return withBaseline . replace (
110+ / < \/ b o d y \s * > (? ! [ \s \S ] * < \/ b o d y \s * > ) / i,
111+ `<script>${ OVERLAY_SCRIPT } </script></body>` ,
112+ ) ;
68113}
69114
70115/**
0 commit comments