The snapshot algorithm
Normative companion to The Malleable HTML File Specification, section 2. Version 1, draft. July 2026. Public domain (CC0).
The main specification names the steps; this page fixes their exact behavior and order. Two independently written clients that both follow this page produce the same bytes from the same DOM. When prose and fixtures disagree, the conformance fixtures win.
The two artifacts
One capture pipeline produces two artifacts:
- the snapshot: the full serialized state of the live page (steps 1 through 6),
- the document: the snapshot taken through the save-time steps (7 and 8). This is the only artifact that reaches disk.
The algorithm
A client must perform these steps in this order.
1. Settle. Give the page a chance to reach a clean boundary before capture. A client that batches undo history must flush any open batch here, so the captured state never straddles an undo boundary. A client with no such machinery skips this step.
2. Clone. Deep-clone document.documentElement. Never mutate the live DOM during capture; every following step operates on the clone. If cloning can trigger page-defined side effects (custom element callbacks, clone guards), the client must suppress them for the duration of the clone.
3. Sync form state into markup. Live form values exist only in JavaScript object properties and would not survive serialization. Reflect them into the clone's attributes and content:
| Control | Rule |
|---|---|
<input type=text> and other textual inputs |
set the value attribute to the live .value |
<input type=checkbox> and type=radio |
set or remove the checked attribute per the live .checked |
<select> |
set the selected attribute on the option matching the live selection; remove it from the others |
<textarea> |
replace child text with the live .value |
<input type=password> |
never written into markup |
<input type=file> |
cannot be persisted; never written into markup |
A client may scope this to elements that opt in (the reference client uses a persist marker) or apply it broadly, but the password and file rules are absolute.
4. Run the page's pre-snapshot hooks. The document may reshape the clone before capture. The reference client supports both registered callbacks and an inline onbeforesnapshot attribute, invoked with the element as this. Hooks see the clone, never the live DOM.
5. Strip [no-snapshot] elements. Remove every element bearing the no-snapshot marker from the clone. This content exists only on the live page and never leaves it: not to disk, not to sync, not to comparison.
6. Strip extension debris. Browser extensions (password managers, grammar checkers) inject elements and stamp attributes into pages they touch. Remove known debris: injected elements that are not part of the document, and marker attributes stamped onto real elements. This step is a SHOULD with teeth: in a format where the file is the database, anything not removed here is saved into the document forever. The conformance fixtures include representative debris.
The serialization of the clone at this point, prefixed with <!DOCTYPE html>, is the snapshot. A client implementing live sync emits it here, before any save-time stripping.
7. Run the page's save-time hooks, then strip [no-save] elements. Save-time hooks (the reference client's inline onbeforesave attribute, then its registered save callbacks) run only when producing a document, never for a bare snapshot. Then remove every element bearing the no-save marker. This content is real page state that other live editors may see through sync, but it never reaches disk.
8. Serialize. The document is:
"<!DOCTYPE html>" + clone.outerHTML
The doctype string is emitted literally. The format is HTML5; whatever doctype the source file carried, the saved document begins <!DOCTYPE html>. Nothing outside the root <html> element survives: comments or processing instructions above the root are not durable state.
Required properties
- Determinism. The same DOM through the same steps yields the same bytes. No timestamps, no random identifiers, no environment-dependent output introduced by the client.
- Stability. Serializing a document, loading it, and serializing it again with no edits in between must produce identical bytes (a fixed point). A client whose capture pipeline injects or reorders markup on every pass fails this.
- The live DOM is untouched. After capture, the live page is byte-for-byte the page from before capture. All mutation happens on clones.
- Host-injected attributes are the host's business, not the client's. A client serializes the root element as it finds it and removes nothing: an ephemeral attribute (such as
savetoken) is the host's to strip on write, and a durable identity attribute (such asdocumentid) is meant to travel back and be stored. Either way the client passes it through untouched. What a client must not do is invent its own persistent markers on<html>without the document's knowledge.
Marker summary
| Marker | Stripped from snapshot | Stripped from document | Meaning |
|---|---|---|---|
no-snapshot |
yes | yes | never leaves the live page |
no-save |
no | yes | live state, shared with editors, never on disk |
Both are attribute markers matched by presence. Clients must strip both; a client that strips only no-save leaks live-only content into sync, and a client that strips neither corrupts the saved file with interface chrome.
Fixtures
The conformance fixtures are input documents, the expected snapshot bytes, and the expected document bytes for each. A client passes when its output is byte-identical. The fixtures are the arbiter; this prose is the explanation.
The fixtures are indexed at malleablehtmlfile.com/fixtures/manifest.json.
Canonical: malleablehtmlfile.com/snapshot-algorithm.txt · Main specification: The Malleable HTML File Specification