Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it, expect } from "vitest";
import { buildFinalFrameContent } from "../frame-content";

describe("buildFinalFrameContent", () => {
// Websandbox rejects frame content without a literal "<head>" and injects
// its bootstrap at that tag.
it.each([
['<html lang="en"><head lang="en"><title>T</title></head><body>x</body></html>'],
["<HTML><HEAD><TITLE>T</TITLE></HEAD><BODY>x</BODY></HTML>"],
])("normalizes a head tag websandbox would not find: %s", (html) => {
const frame = buildFinalFrameContent(html);
expect(frame).toContain("<head>");
expect(frame.indexOf("Content-Security-Policy")).toBeGreaterThan(
frame.indexOf("<head>")
);
expect(frame.indexOf("Content-Security-Policy")).toBeLessThan(
frame.search(/<title>/i)
);
});

it("does not mistake <header> for the head", () => {
const frame = buildFinalFrameContent("<body><header>Top</header></body>");
expect(frame.indexOf("<head>")).toBe(0);
expect(frame).toContain("<header>Top</header>");
expect(frame.indexOf("Content-Security-Policy")).toBeLessThan(
frame.indexOf("<header>")
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,16 @@ export const CSP_META_TAG = `<meta http-equiv="Content-Security-Policy" content=
https://unpkg.com;
">`;

const HEAD_OPEN_TAG = /<head(?:\s[^>]*)?>/i;

/**
* Websandbox requires a literal lowercase "<head>" in its frame content and
* injects its bootstrap there, so "<HEAD>" or "<head lang=...>" is normalized
* (attributes on <head> have no effect) and a missing head is added.
*/
export function ensureHead(html: string): string {
if (/<head[\s>]/i.test(html)) return html;
if (html.includes("<head>")) return html;
if (HEAD_OPEN_TAG.test(html)) return html.replace(HEAD_OPEN_TAG, "<head>");
return `<head></head>${html}`;
}

Expand All @@ -58,11 +66,7 @@ export function buildFinalFrameContent(html: string, css?: string): string {
DESIGN_SYSTEM_STYLE_TAG +
(css ? `<style>${css}</style>` : "");
const withHead = ensureHead(html);
const openTag = withHead.match(/<head[^>]*>/i);
if (!openTag || openTag.index === undefined) {
return `<head>${headContent}</head>${withHead}`;
}
const insertAt = openTag.index + openTag[0].length;
const insertAt = withHead.indexOf("<head>") + "<head>".length;
return withHead.slice(0, insertAt) + headContent + withHead.slice(insertAt);
}

Expand Down