fix: preserve mermaid svg styles under tauri csp

This commit is contained in:
lucaronin
2026-04-30 17:30:04 +02:00
parent 687d6403a3
commit fbb0927401
3 changed files with 46 additions and 1 deletions

View File

@@ -1,5 +1,6 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { RUNTIME_STYLE_NONCE } from '../lib/runtimeStyleNonce'
import { MermaidDiagram } from './MermaidDiagram'
const mermaidMock = vi.hoisted(() => ({
@@ -50,6 +51,26 @@ describe('MermaidDiagram', () => {
expect(screen.getByTestId('mermaid-diagram-dialog-viewport').querySelector('svg')).not.toBeNull()
})
it('tags Mermaid SVG style elements with the runtime CSP nonce', async () => {
mermaidMock.render.mockResolvedValueOnce({
svg: '<svg aria-label="Rendered Mermaid"><style>.node{fill:#000}</style><g><text>A to B</text></g></svg>',
})
render(
<MermaidDiagram
diagram={'flowchart LR\nA --> B'}
source={'```mermaid\nflowchart LR\nA --> B\n```'}
/>,
)
await waitFor(() => {
expect(screen.getByTestId('mermaid-diagram-viewport').querySelector('style')).not.toBeNull()
})
const style = screen.getByTestId('mermaid-diagram-viewport').querySelector('style')
expect(style?.getAttribute('nonce')).toBe(RUNTIME_STYLE_NONCE)
})
it('falls back to the original source when Mermaid cannot render', async () => {
mermaidMock.render.mockRejectedValueOnce(new Error('parse error'))

View File

@@ -8,6 +8,7 @@ import {
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { RUNTIME_STYLE_NONCE } from '@/lib/runtimeStyleNonce'
type MermaidApi = typeof import('mermaid')['default']
@@ -51,6 +52,18 @@ function initializeMermaid(mermaid: MermaidApi) {
initialized = true
}
function withRuntimeStyleNonce(svg: string): string {
if (!svg.includes('<style')) return svg
if (typeof document === 'undefined') return svg
const container = document.createElement('div')
container.innerHTML = svg
container.querySelectorAll('style').forEach((style) => {
style.setAttribute('nonce', RUNTIME_STYLE_NONCE)
})
return container.innerHTML
}
async function renderMermaidDiagram({
diagram,
renderId,
@@ -62,7 +75,7 @@ async function renderMermaidDiagram({
const mermaid = (await import('mermaid')).default
initializeMermaid(mermaid)
const result = await mermaid.render(renderId, diagram)
return result.svg
return withRuntimeStyleNonce(result.svg)
}
const nextRender = renderQueue.then(render, render)
renderQueue = nextRender.then(() => undefined, () => undefined)