Skip to content

Fix PDF export file URI handling and lifecycle - #12

Merged
Bowlerr merged 3 commits into
mainfrom
pdf-fix
Jul 12, 2026
Merged

Fix PDF export file URI handling and lifecycle#12
Bowlerr merged 3 commits into
mainfrom
pdf-fix

Conversation

@Bowlerr

@Bowlerr Bowlerr commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • encode the temporary HTML file URI before passing it to Chromium
  • validate the exact page navigation used for printing so Chromium error pages are rejected
  • wait for local images and fonts before accepting the rendered document
  • stage PDF output and copy it to the requested destination only after validation
  • keep temporary HTML and related resources alive for the full export, then clean them up in a finally block
  • add PDF diagnostics to the Offline Markdown Preview output channel
  • refresh the E2E sample PDF with output from the fixed exporter

Root cause

PDF export called sourceHtmlUri.toString(true). In VS Code's URI API, true skips encoding, so spaces, #, %, and non-ASCII characters in temporary paths could be interpreted as URL syntax by Chromium. Chromium still exited successfully and wrote a PDF containing its ERR_FILE_NOT_FOUND page, and the extension treated that as a successful export.

Preview rendering was unaffected because it uses VS Code webview URIs. HTML export was unaffected because it writes the standalone HTML directly and never asks Chromium to navigate to the temporary file.

User impact

PDF exports now contain the rendered preview for normal, nested, spaced, encoded, and Unicode paths. Navigation or rendering failures produce a useful extension error instead of overwriting the target with a Chromium error-page PDF. Existing preview and HTML export behavior is preserved.

Validation

  • npm test — 124 tests passed
  • npm run lint — passed
  • npm run build — passed
  • npm run test:e2e — completed successfully using cached VS Code 1.110.1
  • git diff --check — passed
  • manual Chromium PDF smoke test with spaces, #, %, Unicode, a local SVG, styles, and fonts — passed
  • inspected the generated PDF using pdfinfo, pdftotext, and rasterized visual output

Notes

Direct PDF export still requires a locally installed Chrome, Edge, or Chromium executable.

Summary by CodeRabbit

  • Bug Fixes
    • Improved PDF export reliability by performing headless, page-readiness checks (including image decoding and font readiness) before printing.
    • Enhanced PDF export error reporting with clearer, readiness/navigation-specific failure reasons; removed fallback to opening printable HTML when direct export fails.
    • Ensured temporary export files are cleaned up after both successful and failed exports.
  • Tests
    • Expanded unit tests for PDF and standalone HTML exports, including URI/path encoding edge cases and headless navigation/readiness validation paths.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

PDF export now stages temporary HTML and PDF files, signals document readiness, validates Chromium navigation and output, captures diagnostics, and guarantees cleanup. Tests cover URI handling, readiness HTML, successful export, classified failures, and filesystem interactions.

Changes

PDF export pipeline

Layer / File(s) Summary
HTML readiness and process diagnostics
src/extension/preview/PreviewPanel.ts, test/unit/helpers/vscodeMock.ts
PDF HTML waits for document, image, and font readiness; navigation helpers classify failures; subprocess output is captured in a structured result.
Headless export orchestration
src/extension/preview/PreviewPanel.ts
Export stages temporary HTML and PDF files, validates Chromium navigation and PDF size, logs diagnostics, copies successful output, and cleans up temporary files.
PDF export test coverage
test/unit/previewPanel.test.ts
Mocks and tests cover URI encoding, readiness markers, navigation errors, successful writes, failure reporting, and cleanup.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PreviewController
  participant FileSystem
  participant Chromium
  participant OutputChannel
  PreviewController->>FileSystem: write temporary print HTML
  PreviewController->>Chromium: navigate to local PDF HTML
  Chromium-->>PreviewController: return DOM dump and staged PDF
  PreviewController->>PreviewController: validate readiness and PDF size
  PreviewController->>OutputChannel: write PDF diagnostics
  PreviewController->>FileSystem: copy final PDF and remove temporary files
Loading

Possibly related PRs

Poem

A rabbit watched the PDF take flight,
Through waiting fonts and images bright.
Chromium hopped, the markers shone,
Diagnostics found what had gone wrong.
Temp files vanished—clean and neat. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarises the main change to PDF export URI handling and lifecycle management.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pdf-fix

Comment @coderabbitai help to get the list of available commands.

@Bowlerr
Bowlerr marked this pull request as ready for review July 12, 2026 14:57
@Bowlerr

Bowlerr commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/extension/preview/PreviewPanel.ts`:
- Around line 1959-1982: Update the PDF readiness flow around the Chromium
invocation and getPdfNavigationError so readiness is established reliably before
the dumped DOM is inspected. Either make the page’s data-omv-pdf-ready signal
synchronous or explicitly wait for the image.decode() and document.fonts.ready
promises to settle before evaluating the dump, while preserving navigation-error
reporting for genuinely incomplete exports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ba506699-97ea-4500-89a7-e8e5a091c5a2

📥 Commits

Reviewing files that changed from the base of the PR and between 0b483c2 and ba3c304.

⛔ Files ignored due to path filters (1)
  • .e2e-workspace/sample.pdf is excluded by !**/*.pdf
📒 Files selected for processing (3)
  • src/extension/preview/PreviewPanel.ts
  • test/unit/helpers/vscodeMock.ts
  • test/unit/previewPanel.test.ts

Comment thread src/extension/preview/PreviewPanel.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/extension/preview/PreviewPanel.ts`:
- Around line 1949-1963: Split the Chromium export around the existing
runProcess call into separate invocations for DOM dumping and PDF printing,
preserving the required shared browser flags and arguments for each action.
Ensure the PDF invocation uses --no-pdf-header-footer instead of
--print-to-pdf-no-header and still targets targetPdfUri.fsPath.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 523c4e7f-ed68-4f41-8e2a-c8d620050214

📥 Commits

Reviewing files that changed from the base of the PR and between 0b483c2 and ba3c304.

⛔ Files ignored due to path filters (1)
  • .e2e-workspace/sample.pdf is excluded by !**/*.pdf
📒 Files selected for processing (3)
  • src/extension/preview/PreviewPanel.ts
  • test/unit/helpers/vscodeMock.ts
  • test/unit/previewPanel.test.ts

Comment thread src/extension/preview/PreviewPanel.ts Outdated
@Bowlerr

Bowlerr commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@Bowlerr

Bowlerr commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/extension/preview/PreviewPanel.ts (1)

2342-2388: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider a hard timeout for the spawned Chromium process. runProcess resolves only on close/error, with no upper bound. --virtual-time-budget caps render work, but a Chromium binary that stalls at startup (profile lock, GPU probe, crashpad handler, etc.) would leave exportPdf awaiting indefinitely with no cancellation path. A watchdog that kills the child and resolves with a distinct failure code keeps the export command responsive.

♻️ Sketch: add a watchdog timeout
   return new Promise((resolve) => {
     let stdout = '';
     let stderr = '';
     let settled = false;
     const child = spawn(command, args, {
       stdio: ['ignore', 'pipe', 'pipe']
     });
+    const timer = setTimeout(() => {
+      if (settled) return;
+      settled = true;
+      child.kill('SIGKILL');
+      resolve({ ok: false, code: 'ETIMEDOUT', stdout, stderr });
+    }, PDF_EXPORT_PROCESS_TIMEOUT_MS); // e.g. virtual-time-budget + generous startup margin

Remember to clearTimeout(timer) in the error and close handlers before resolving.

Please confirm the intended failure semantics (retry next candidate vs. abort) so the timeout code slots cleanly into the existing ENOENT/status handling in tryHeadlessPdfExport.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension/preview/PreviewPanel.ts` around lines 2342 - 2388, Update
runProcess to add a hard watchdog timeout for the spawned child process,
clearing the timer in both the error and close handlers before resolving. On
timeout, kill the child, settle only once, and resolve with a distinct timeout
failure code while preserving captured stdout/stderr; ensure
tryHeadlessPdfExport treats that code consistently with existing process
failures, retrying the next candidate rather than hanging or aborting
unexpectedly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/extension/preview/PreviewPanel.ts`:
- Around line 2342-2388: Update runProcess to add a hard watchdog timeout for
the spawned child process, clearing the timer in both the error and close
handlers before resolving. On timeout, kill the child, settle only once, and
resolve with a distinct timeout failure code while preserving captured
stdout/stderr; ensure tryHeadlessPdfExport treats that code consistently with
existing process failures, retrying the next candidate rather than hanging or
aborting unexpectedly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 414d212c-5694-4bb4-8e62-3342f8361643

📥 Commits

Reviewing files that changed from the base of the PR and between 0b483c2 and 93fcdfd.

⛔ Files ignored due to path filters (1)
  • .e2e-workspace/sample.pdf is excluded by !**/*.pdf
📒 Files selected for processing (3)
  • src/extension/preview/PreviewPanel.ts
  • test/unit/helpers/vscodeMock.ts
  • test/unit/previewPanel.test.ts

@Bowlerr
Bowlerr merged commit 74c4314 into main Jul 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export PDF error: Your file couldn’t be accessed It may have been moved, edited, or deleted. ERR_FILE_NOT_FOUND

1 participant