MarkUp x snapdom

snapdom capture limits

Each section triggers one known limitation of snapdom, the library behind MarkUp's comment screenshots. Pin a comment on the marker in each fixture, then compare the screenshot with what you see here.

Versions in play

Where snapdom
Preview env (mar-4055-snapdom-markup-website): proxy-bundle and markup-sdk 3.0.0
develop: markup-sdk screenshot plugin 2.24.3
Upstream latest 3.4.0

How to test

  1. Turn on the SCREENSHOT_CLIENT_DOM workspace flag and disable the MarkUp extension. With the extension on, MarkUp takes a native tab screenshot and none of this applies.
  2. Create a website markup of this page in the preview environment.
  3. In each fixture, click the + pin here marker. Pin captures crop to about 55% of the viewport around the click, so the marker sits in the middle of the fixture.
  4. Sections tagged Picker mode also need a Take screenshot of the named element.
  5. Compare each screenshot with the live fixture and its "In MarkUp" line.

Any version snapdom can't do this Mitigated by MarkUp our proxy works round it Fixed in x.y.z broken in a version we ship Safari browser-specific Picker mode needs Take screenshot

A. Can't capture, any version

These come from how snapdom works: it copies the DOM into an SVG <foreignObject> and lets the browser re-draw the copy. It never takes a real screenshot.

A1

Cross-origin iframes

Any version Mostly mitigated by MarkUp

snapdom can't read inside a frame from another origin, so it paints a grey placeholder. MarkUp's proxy rewrites third-party iframes and asks its bundle inside each one to capture itself, which hides this in most cases. These six frames include the ways round that.

In MarkUp (preview, 3.0.0 via proxy)
example.com and YouTube: real content (nested capture). sandbox: placeholder (no scripts, so the frame never answers). data: URL: placeholder (opaque origin). frame.html: its green page shows, but the example.com inside it is a placeholder (nested capture doesn't recurse). srcdoc: renders.
Raw snapdom / SDK (no proxy)
Every cross-origin frame is a placeholder. frame.html and srcdoc render, with a placeholder for the example.com inside frame.html.

Issues: #27, #155, #222

example.com
YouTube embed
example.com, sandbox=""
frame.html, embeds example.com
srcdoc (same origin, control)
data: URL

A2

Images without CORS headers

Any version Mitigated by MarkUp

snapdom re-downloads every image so it can embed it. If the image's server sends no CORS headers, it can't, even though the browser displays the image fine. google.com sends none; placehold.co does.

In MarkUp (preview, 3.0.0 via proxy)
All four render. MarkUp sets snapdom's useProxy to its /__markup_cors__ relay, which fetches no-CORS images on the server.
Raw snapdom / SDK (no proxy)
The Google <img> becomes a grey placeholder and the Google CSS background disappears: the SDK's captureElement passes no useProxy. placehold.co and the local SVG render.

Issues: #78, #239, #295, #347

<img>, google.com, no CORS Google logo, served without CORS
CSS background, google.com, no CORS
<img>, placehold.co, CORS CORS OK placeholder
<img>, same origin (control) Local SVG

A3

Tainted canvas

Any version

A canvas that has drawn a cross-origin image without CORS is tainted: the browser stops every script from reading its pixels, snapdom included. Charts and maps that draw remote tiles hit this.

In MarkUp (preview, 3.0.0 via proxy)
The left canvas is blank or a placeholder. The relay can't help, because the pixels are already locked inside the canvas. The right canvas renders.
Raw snapdom / SDK (no proxy)
Same.

Issues: #199

Draws the google.com logo (no CORS) Loading logo...
Draws only its own shapes (control) toDataURL() works

A4

Video and audio

Any version

snapdom grabs a video's current frame by drawing it to a canvas. A cross-origin video without CORS taints that canvas, so snapdom falls back to the poster image. Audio players are re-drawn, not captured.

In MarkUp (preview, 3.0.0 via proxy)
Left: the flower frame you see. Middle: the orange POSTER, not the bunny frame you see (MarkUp's relay only passes images, fonts and CSS). Right: a drawn stand-in for the audio controls, which may not match your browser's player.
Raw snapdom / SDK (no proxy)
Same.

Issues: #92, #277, #444

Same-origin video, paused
Cross-origin video, no CORS, paused at 3s
<audio controls>

A5

Canvas not painted yet

Any version

snapdom copies a canvas's pixels at the moment of capture. Charts, maps and Lottie animations that are still loading come out empty. This canvas alternates: 5 seconds blank, 5 seconds painted.

In MarkUp (preview, 3.0.0 via proxy)
Whatever the canvas holds when you pin. Pin while the label says Blank and the chart area is empty, even if it paints a moment later. The label itself is ordinary text and always shows.
Raw snapdom / SDK (no proxy)
Same.

Issues: #486

2D canvas "chart", slow to load Phase: starting

A6

Fonts loaded with FontFace()

Any version

Fonts loaded with the JavaScript FontFace() API have no CSS rule pointing at the file, so snapdom can't find the file to embed. Both lines use the same Bungee font file.

In MarkUp (preview, 3.0.0 via proxy)
The top line falls back to Courier. MarkUp waits for every font in document.fonts, but its localFonts list only reads @font-face rules. The bottom line renders in Bungee.
Raw snapdom / SDK (no proxy)
Same.

Issues: #43, #85, #238

new FontFace('BungeeJS', ...)

FontFace API

Loading...
@font-face { font-family: 'BungeeCSS' } (control)

@font-face rule

A7

Unstyled native form controls

Any version Firefox

Unstyled form controls are painted by the operating system's theme. snapdom copies their computed CSS instead, which the browser then draws literally. The maintainer calls this a known limitation.

In MarkUp (preview, 3.0.0 via proxy)
Borders on inputs, buttons and selects look heavier or darker than live. In Firefox, checkboxes and radios are drawn replacements. In mobile Firefox, placeholder and select text may sit in the wrong place.
Raw snapdom / SDK (no proxy)
Same.

Issues: #290, #427, #516

A8

Safari drops images

Any version Safari / iOS

Safari sometimes paints an SVG foreignObject before its embedded images have decoded, so images and background images go missing, most often on the first capture. The maintainer traces this to a WebKit bug.

In MarkUp (preview, 3.0.0 via proxy)
Chrome and Firefox: all five tiles and the emoji present. Safari and iOS: one or more tiles may be blank, most likely on the first pin after loading. MarkUp runs its first capture twice as a warm-up, which may hide this.
Raw snapdom / SDK (no proxy)
Same, and more likely to show without the warm-up.

Issues: #129, #330, #394

<img> SVG, same origin Local SVG
<img> PNG, same origin Local PNG
<img> PNG, CORS CORS PNG
CSS background, same origin
CSS background, CORS
Emoji

📸 🎯 ✅ 🧪

A9

Not pixel-perfect

Any version Picker mode

snapdom re-draws a copy of the page rather than taking a real screenshot, so small differences creep in.

In MarkUp (preview, 3.0.0 via proxy)
(a) Pin: fine. Picker on the purple card: captured at 70% of its size, because its ancestor is scaled. (b) The orange custom scrollbar is missing or native; the scroll position should be kept. (c) Each cyan line fits with 0.25px to spare, so a line may wrap its last word in the capture.
Raw snapdom / SDK (no proxy)
Same.

Issues: #135, #214, #334, #349, #458

(a) Ancestor transform: scale(0.7)
Pick this card Its parent has transform: scale(0.7).
(b) Custom scrollbar, scrolled halfway
line 01
line 02
line 03
line 04
line 05
line 06
line 07
line 08
line 09
line 10 (middle)
line 11 (middle)
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
(c) Lines sized to fit with 0.25px to spare

The quick brown fox jumps over the lazy dog.

Sphinx of black quartz, judge my vow, said Wilf.

Pack my box with five dozen liquor jugs, please.

A10

Closed shadow roots

Any version Picker mode

A web component with a closed shadow root hides its contents from every page script. snapdom only flattens open shadow roots. No issue reports this; the features doc only promises open ones.

In MarkUp (preview, 3.0.0 via proxy)
Left: an empty dashed box where the card should be. The picker can't select inside it either. Right: the open card renders.
Raw snapdom / SDK (no proxy)
Same.

Reference: FEATURES.md, Capture & clone

<closed-card>, mode: 'closed'
<open-card>, mode: 'open' (control)

A11

Fonts in a customer-CDN stylesheet

Any version, including 3.4.0

snapdom only reads @font-face rules from a cross-origin stylesheet if its host is on a fixed list of font CDNs (Google Fonts, Typekit, Font Awesome, jsDelivr, unpkg, cdnjs, esm.sh) or in the fontStylesheetDomains option, or its URL mentions "font" or the family name. A customer's own CDN usually matches none of those, and MarkUp's localFonts can't read the sheet either. The stylesheet is linked without crossorigin, so MarkUp's proxy leaves it alone. The 3.3.5 change that reads cross-origin sheets only applies to sheets that pass this filter.

In MarkUp (preview, 3.0.0 via proxy)
The top line falls back to Courier. The bottom line (same font, same-origin stylesheet) renders in a script face.
Raw snapdom / SDK
Same in 2.24.3, 3.0.0 and 3.4.0. With fontStylesheetDomains set to the stylesheet's host, all three embed the font (try &fsd=<host> with the harness). MarkUp could pass the hosts of every cross-origin stylesheet on the page.

Issues: #309

Stylesheet on a second hostname

Acme Display

Same font, same-origin stylesheet (control)

Acme Display

B. Fixed upstream, broken in a version we ship

These are bugs, not hard limits. Each one is fixed in a later snapdom, so the result depends on which version captures the page. Use ?harness=3.4.0 locally to see the fixed behaviour.

B1

Font families containing __

Fixed in 3.4.0 Broken in 3.0.0 and 2.24.3

next/font names fonts like __Inter_d65c78. snapdom builds an internal key as family__weight__style and splits it on __, so these families never match and never get embedded. Both lines use the same Monoton file.

In MarkUp (preview, 3.0.0 via proxy)
The top line falls back to Courier. This also shows whether MarkUp's localFonts list gets round the bug; we expect not. The bottom line renders in Monoton.
Raw snapdom / SDK
2.24.3 and 3.0.0: same. 3.4.0: both lines render in Monoton.

Issues: #542

font-family: '__Monoton_d65c78'

Next font

font-family: 'Monoton Control' (control)

Next font

B2

WebGL in a hidden tab

Fixed in 2.24.7 Broken in 2.24.3 (develop SDK)

For WebGL canvases with preserveDrawingBuffer: false, snapdom 2.24.3 waits for the next animation frame before reading the pixels, with no timeout. Browsers don't run animation frames in a hidden tab, so a capture started just before switching tabs hangs.

In MarkUp (preview, 3.0.0 via proxy)
Captures the spinning triangle normally. The fix is in 2.24.7, so only develop's SDK (2.24.3) is affected.
Raw snapdom / SDK 2.24.3
Start a capture and switch tabs straight away: it doesn't finish until you come back. The harness's Capture in 3s button does this for you.

Issues: #480, #486

WebGL, preserveDrawingBuffer: false, redrawn every frame

B3

Blank web-font text on the first capture

Fixed in 3.1.1 Broken in 3.0.0 and 3.1.0 Chrome / Edge

In 3.0.0 and 3.1.0 on Chromium, snapdom draws into the canvas one task before its embedded fonts finish loading, so web-font text comes out invisible on the first capture. 2.24.3 doesn't have this bug, but the preview runs 3.0.0. This font is used nowhere else on the page.

In MarkUp (preview, 3.0.0 via proxy)
Reload the page and make this your first pin. In Chrome or Edge the text may be missing. MarkUp runs its first capture twice as a warm-up, which may hide it; that's worth knowing either way.
Raw snapdom / SDK
3.0.0 in Chrome: the first capture after a reload may lose the glyphs. 2.24.3 and 3.4.0: text renders.

Issues: #506

@font-face 'PressStart Local', same origin

First capture
after a reload

B4

Google Fonts with :lang()

Fixed in 2.24.16 Broken in 2.24.3 (develop SDK)

The repro from #493: Google's Noto families, loaded with variable weight ranges and picked per language with :lang(). Up to 2.24.15 snapdom failed to embed them. Weight 100 (thin) makes a fallback obvious.

In MarkUp (preview, 3.0.0 via proxy)
All five lines render in thin Noto. 3.0.0 includes the fix.
Raw snapdom / SDK 2.24.3
The four CJK lines disappear completely and the English line falls back to Courier. In a local run the capture also took about 16 seconds.

Issues: #493

  • 简体中文测试字体
  • 繁體中文測試字型
  • 日本語フォントテスト
  • 한국어 폰트 테스트
  • English test font

C. Separate pages

MarkUp's pin capture snapshots the whole page, then crops it. These cases would slow down or interfere with every other capture, so each one has its own page.

C1. Very large page

Any version

About 3,000 rows plus one element 40,000px tall. Locally, a viewport capture of the whole page took about 0.2s, so size alone may not slow pins much. Picking the giant element hits the browser's 32,767px canvas limit, so it comes out shrunk.

C2. Strict meta CSP

Untested risk

A customer page with a <meta> Content-Security-Policy. MarkUp's proxy strips CSP headers but not meta tags, so its bundle or snapdom's fetches may be blocked. The result is unknown; that's the experiment.

C3. CSSOM-only edits

v3 only

snapdom 3 reuses earlier captures when the DOM hasn't changed. This box changes colour through insertRule alone, so a second capture could come back stale. In local runs it didn't, in any version; this page checks MarkUp behaves the same.