Export leaves its scratch render container in the page layout · Issue #5541 · marktext/marktext · GitHub
Skip to content

Export leaves its scratch render container in the page layout #5541

Closed

Description

@Jocs

Description

Every styled-HTML / PDF / print export appends a scratch container to document.body — a second, full-size copy of the rendered document that the engine uses to rasterise diagrams before reading the markup back. Two things are wrong with how that container is contained.

1. The rule that moves it off-screen selects a class the engine no longer emits.

packages/desktop/src/renderer/src/assets/styles/printService.css still targets the legacy muyajs class:

body .ag-render-container {
  position: fixed;
  left: -10000px;
  right: -10000px;
  max-width: 100%;
}

The @muyajs/core engine tags the container mu-render-container (packages/muya/src/state/markdownToHtml.ts). The selector was never updated when the engine was swapped in for 0.20, so nothing matches and the container is laid out position: static, in the normal flow, visible, at the top of <body>.

Measured while exporting the 365 KB document from #3433, with a MutationObserver reading the container's box the moment it is inserted:

today with the selector corrected
position static fixed
box top: 21, left: 0, 1200 × 228251 top: 0, left: -10000, 1200 × 228251
document.body.scrollHeight 228 243 800

So the host page's layout grows to ~228 000 px for the duration of the export and every export pays a full reflow of the host document. It is not currently visible — the app root is a positioned element and wins the paint order, and body has overflow-y: hidden so no scrollbar appears — and the container is removed within a frame or two, so this is not user-facing today. It is one stacking-context change away from being user-facing, and the containment it is supposed to have is simply not in effect.

2. The container is only removed on the success path.

document.body.appendChild(exportContainer);
await this._renderMermaid();
await this._renderDiagram();
// …
exportContainer.remove();

_renderMermaid / _renderDiagram contain per-diagram failures in their own try blocks, but the await loadRenderer(…) that pulls in mermaid / vega-lite / plantuml sits outside those blocks. A failed dynamic import (chunk fetch failure, a corrupted install) therefore propagates out of renderHtml, exportContainer.remove() never runs, and the container — full-size, in the normal flow, per (1) — is stranded in the page for the rest of the session. The desktop wrapper catches the rejection and shows "Failed to export document", so the user gets an error and a page that is now permanently carrying a copy of their document.

Steps to reproduce

(2) reproduces headlessly with the diagram loader mocked to reject the way a failed chunk load does:

vi.mock('../../utils/diagram', () => ({ default: loadRenderer }))
loadRenderer.mockRejectedValue(new Error('Failed to fetch dynamically imported module'))

await expect(new MarkdownToHtml('```mermaid\ngraph TD; A-->B\n```\n').renderHtml()).rejects.toThrow()
expect(document.querySelectorAll('.mu-render-container')).toHaveLength(0)
//   → AssertionError: expected …(1) to have a length of +0 but got 1

Expected behavior:

The scratch container stays out of the host document's layout while it is live, and is gone whether the render succeeds or fails.

Actual behavior:

It is laid out in the normal flow for the duration of every export, and is left behind permanently if a diagram renderer fails to load.

Versions

  • MarkText version: 0.20.0-dev (develop, 978d71d)
  • Operating system: macOS (the code path is platform independent)

Found while investigating #3880.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐛 bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions