SingleFile CLI 2.10.0
New features
--max-appended-data-lengthsets the budget for the data appended after the compressed data of a self-extracting HTML file, 16361 bytes by default (it was 65535 until now). Readers tolerate trailing bytes only as far back as their end-of-archive scan reaches, and that varies by an order of magnitude: 65557 bytes for Pythonzipfile, 32768 for perlArchive::Zip, but 16383 for libarchive. The new default fits the narrowest window, so archives that used to fit the budget and still be unreadable by libarchive now are. Data over the limit is relocated ahead of the compressed data instead, as it already was above the old limit
CLI fixes and improvements
- The scripts passed to
--browser-scriptare joined with a newline. They were concatenated with nothing between them, so a file that did not end with a newline ran into the next one, and a file ending on a//comment commented out its first line - The options forwarded to the multi-page archive of
--crawl-save-archiveare derived from the list of compression options declared by single-file-core, instead of a list written by hand. A new option added to the core no longer needs a second edit here to reach that path, where it was silently ignored - With
--browser-engine firefox, the assignment exposing SingleFile to the injected scripts is inserted after the bundle instead of being produced by rewriting it. The rewrite matched a declaration in the minified bundle with a regular expression, so a change in the way it is minified would have broken the injection silently - The help text of
--max-resource-sizesays that the limit applies to every fetched resource, images, fonts, stylesheets, scripts, frames, videos and audios, and not only to the four types it used to name. The help text of--block-mixed-contentsays which resources it blocks
Fixes from single-file-core 1.5.125 and 1.5.126
- An iframe holding an SVG document keeps its content. Such a document has no
body, and the nesting check readbody.tagNamebefore anything else, so the frame threw and silently contributed nothing to the save - A sandboxed
srcdociframe is saved as it renders. The frame answers for itself, but its answer was discarded because the parent had already fallen back to re-parsing thesrcdocattribute, which no script has run against - Only the winning
@font-facerule is embedded when a page declares the same face more than once. Rules were de-duplicated by family and source together, so the same family, style and weight declared twice with different sources embedded both fonts, although the cascade only ever uses the last one. A source repeated inside a single rule is also embedded once, in the position its later declaration gives it - A
@font-facerule left with no usable source is removed, instead of being written out assrc:with nothing after it, which is an invalid declaration - Alternative font sources are narrowed by content type again. The font resources were not passed to the code that reads them, so a face whose sources carry no
format()kept every alternative in the archive instead of the best one - A BMP or a GIF87a served without a content type is stored with its real extension. Both signatures were wrong,
"NM"instead of"BM"and"GIF8;a"instead of"GIF87a", so neither format was ever recognised. The AVI signature was the one for WAV - With
--block-mixed-content, a resource at ablob:URL is kept on an https page. The check compared the scheme againsthttps:and a blob URL starts withblob:, although it inherits the origin of the page that created it - An image whose
srcsetcould not be saved no longer keeps an emptysrcsetandsizespair. Both attributes are removed, so the browser falls back tosrcinstead of resolving an empty candidate list - A canvas displaying its captured drawing pins
background-attachment, so a page rule setting it tofixedno longer stretches or crops the image - A frame the save leaves empty no longer carries a
sandboxattribute that can no longer apply to anything - A resource fetched through the frame fallback carries the referrer of the page it belongs to
Imagekeeps a stable identity while a page is captured. The hook installed a getter that built a new function on every read, soImage === Imagewas false,instanceof Imagefailed for images the page had created, andImage.toString()revealed the wrapper.Element.prototype.getBoundingClientRect,CSS.paintWorklet.addModuleand the hookedIntersectionObserver,FontFace,CSSStyleSheetanddocument.fontsfunctions report their native name, arity and source text as well- A self-extracting archive opened from the filesystem reads itself when the browser allows it. The whole-file read every other scheme uses was skipped outright for
file://URLs, leaving only the embedded recovery payload. Firefox performs the read and now opens archives saved without a payload; Chromium and Safari refuse it and fall back to the previous behaviour unchanged
Co-authored by Claude (Claude Code)