Skip to content

Commit 0ab82c6

Browse files
committed
fix(wasm): harden marimo inline Graphviz rendering for srcdoc and UTF-8 DOT
1 parent f808d19 commit 0ab82c6

2 files changed

Lines changed: 36 additions & 5 deletions

File tree

corneto/contrib/_util.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ def dot_wasm_html(
9292
<script type="module">
9393
(async () => {{
9494
const target = document.getElementById("{container_id}");
95+
const setTargetHtml = (markup) => {{
96+
if (target) {{
97+
target.innerHTML = markup;
98+
}}
99+
}};
100+
if (!target) {{
101+
return;
102+
}}
103+
const decodeBase64Utf8 = (encoded) => {{
104+
const binary = atob(encoded);
105+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
106+
return new TextDecoder("utf-8").decode(bytes);
107+
}};
95108
const resizeFrameToContent = () => {{
96109
try {{
97110
let h = target ? target.scrollHeight : 0;
@@ -107,7 +120,7 @@ def dot_wasm_html(
107120
window.frameElement.style.height = `${{h}}px`;
108121
}}
109122
}} catch (_err) {{
110-
// best effort only
123+
/* best effort only */
111124
}}
112125
}};
113126
try {{
@@ -117,15 +130,15 @@ def dot_wasm_html(
117130
throw new Error("Graphviz WASM module does not expose Graphviz.load()");
118131
}}
119132
const graphviz = await Graphviz.load();
120-
const dot = atob("{dot_string_base64}");
133+
const dot = decodeBase64Utf8("{dot_string_base64}");
121134
const svg = await graphviz.dot(dot, "svg", "dot");
122-
target.innerHTML = svg;
135+
setTargetHtml(svg);
123136
resizeFrameToContent();
124137
requestAnimationFrame(resizeFrameToContent);
125138
setTimeout(resizeFrameToContent, 50);
126139
}} catch (error) {{
127-
target.innerHTML = "<pre style='white-space:pre-wrap;color:#b00020'>Graph rendering failed: " +
128-
String(error) + "</pre>";
140+
setTargetHtml("<pre style='white-space:pre-wrap;color:#b00020'>Graph rendering failed: " +
141+
String(error) + "</pre>");
129142
resizeFrameToContent();
130143
}}
131144
}})();

tests/graph/test_plotting.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ def test_dot_wasm_html_accepts_raw_dot_source():
4242
assert "Graphviz.load" in html
4343

4444

45+
def test_dot_wasm_html_avoids_line_comments_in_inline_script():
46+
html = dot_wasm_html('digraph {"A" -> "B";}')
47+
assert "// best effort only" not in html
48+
assert "/* best effort only */" in html
49+
50+
51+
def test_dot_wasm_html_guards_missing_target_element():
52+
html = dot_wasm_html('digraph {"A" -> "B";}')
53+
assert "if (!target) {" in html
54+
assert "setTargetHtml" in html
55+
56+
57+
def test_dot_wasm_html_decodes_dot_as_utf8():
58+
html = dot_wasm_html('digraph {"á" -> "ß";}')
59+
assert 'new TextDecoder("utf-8").decode(bytes)' in html
60+
assert "decodeBase64Utf8" in html
61+
62+
4563
def test_plot_wasm_works_without_graphviz(monkeypatch):
4664
g = Graph()
4765
g.add_edge("A", "B")

0 commit comments

Comments
 (0)