MutationDiff

Tracks mutations performed on the DOM, giving you the delta between original and mutated DOM, allowing DOM to be reverted to its initial state, or a range to be queried with the extent of DOM mutations. The interface is designed to take input from MutationObserver, but this is up to the user.

Tracking is optimal, in that we only store the delta between the original and current DOM. Reverting the DOM can be done directly, without needing to rewind a log of all mutations. Additionally, mutation range queries give exact bounds, and can detect when mutations cancel out.

Constructor

new MutationDiff()

Construct a new MutationDiff object

Members

props :Map.<Node, PropertyMutations>

Private structure for holding raw attribute, character, or custom property changes. For performance reasons you may access this, but backwards compatibility is not guaranteed. See the source code for usage.

Type:
  • Map.<Node, PropertyMutations>

storage_size :number

For memory optimization: Returns a value indicating the size of internal storage for tracking the mutations. You could use this to trigger periodic reversion/clearing or other mutation processing to keep memory lower.

Type:
  • number

tree :TreeMutations

Private structure for holding raw node additions, deletions, or movements. For performance reasons you may access this, but backwards compatibility is not guaranteed. See the source code for usage.

Type:
  • TreeMutations

Methods

(static) patch_grouped_children(groups)

Moves groups of nodes inside the current DOM to new positions. You can use this to revert nodes' DOM positions, or apply mutated positions to an unchanged DOM. Out-of-the-box this does not support patching an unrelated DOM tree. However, this could be done easily by mapping nodes from one tree to another:

*function remapped_diff(){
	for (const group of diff.diff_grouped_children()){
		group.nodes = group.nodes.map(remap_fn)
		yield group;
	}
}
diff.patch_grouped_children(remapped_diff());

The remap_fn might consult a Map<Node,Node> or fetch a unique identifier for the node that we can find a correspondence to in the other DOM.

Parameters:
NameTypeDescription
groupsIterable.<MutationDiff~DiffPosition>

This can be any iterable, such as an array or generator. The nodes property must be set, so the output of diff will not work; you can use the output of diff_grouped_children.

attribute(node, key, old_value)

Indicate HTML attribute changed. Note this uses the current node.getAttribute() value for detecting when the attribute is modified.

Parameters:
NameTypeDescription
nodeNode

node whose attribute changed

keystring

namespace qualified attribute name, e.g. "namespace:name"; the namespace can be ommitted if not needed

old_valuestring

previous value of this attribute; when attribute is first seen, this is stored as the original value, and used to detect when the attribute reverts

children(parent, removed, added, prevnullable, nextnullable)

Indicate nodes were added or removed as children of some parent node

Parameters:
NameTypeAttributesDescription
parentNode

point-in-time parentNode where removal/insertion occurred

removedArray.<Node>

an ordered list of nodes that were removed

addedArray.<Node>

an ordered list of nodes that were added

prevNode<nullable>

point-in-time previousSibling of the removed/added nodes

nextNode<nullable>

point-in-time nextSibling of the removed/added nodes

clear()

Clear the internal log of mutations, effectively "committing" the current DOM. You may also wish to reattach a corresponding MutationObserver, as it can track DOM nodes outside root. After clearing/reverting, these disconnected trees do not matter anymore. See the MDN documetation for MutationObserver.observe() for details.

custom(node, key, value, old_value)

Indicate some custom property for the node has changed. A custom property is any user defined value derived from, or associated with a node. Custom properties are not automatically reverted; you must provide a callback to revert them yourself, see revert

Parameters:
NameTypeDescription
nodeNode

node whose property changed

key*

any Map compatible key

value*

current value for this property; this can be the value several mutations after old_value was read, as would be the case for MutationRecord

old_value*

previous value of this property; when property is first seen, this is stored as the original value, and used to detect when the property reverts

data(node, old_value)

Indicate data change for a CharacterData node (e.g. text content has changed). Note this uses the current node.data value for detecting when the text is modified.

Parameters:
NameTypeDescription
nodeNode

node whose data (text content) changed

old_valuestring

previous text content; when this node's text is first seen, this is stored as the original value, and used to detect when the text reverts

diff(filteropt, custom_getopt) → {Map.<Node, MutationDiff~Diff>}

Get the current diff. Mutated properties are not cached, so requesting MUTATED will mean the DOM will be queried for the current value. Additionally, you may consider using diff_grouped_children instead for the CHILDREN diff, as that may be more useful for downstream tasks.

Parameters:
NameTypeAttributesDefaultDescription
filternumber<optional>
ALL

A bitmask for which differences to return, as given by MutationDiffFlags

custom_getMutationDiff~customGetCbk<optional>

A callback to fetch the mutated value for custom properties. Only used when filter contains MUTATED and CUSTOM flags. If not provided, their mutated value will not be set.

Returns:

A Map giving the changes for each node. The output may be freely modified, as it is a copied view. For performance, you may consider accessing the raw internal mutation data instead, but backward compatibility is not guaranteed.

Type: 
Map.<Node, MutationDiff~Diff>

(generator) diff_grouped_children(modeopt, include_removedopt)

Generator which yields groups of adjacent nodes whose DOM position was altered. When patching a DOM and rearranging the nodes to new positions, it is necessary to link adjacent nodes first. The reason is that DOM insertions require a reference sibling, e.g. Node.insertBefore(). If a reference sibling has not been patched first, a node could be moved to the incorrect position.

You can fetch groups of nodes for either the original or mutated DOM tree. The groups will not necessarily be the same.

This is implemented as a generator in case you want to process groups as they come and reduce memory usage. You can loop over the return value like any other iterable. If you'd like to get an array instead, simply use:

Array.from(diff.diff_grouped_children())

See MutationDiff.patch_grouped_children for using the result to patch a (possibly different) DOM.

Parameters:
NameTypeAttributesDefaultDescription
modeMutationDiffFlags.ORIGINAL | MutationDiffFlags.MUTATED<optional>
ORIGINAL

whether to group nodes' by their original or mutated positions

include_removedboolean<optional>
true

setting this to true will include an additional group for "removed" nodes: nodes that are not present in the original/mutated DOM

mutated(rootopt) → {boolean}

Check if the DOM is mutated. If the DOM was changed, but the changes put the DOM back in its original state, the DOM is not mutated.

Parameters:
NameTypeAttributesDescription
rootNode<optional>

If provided, only mutations that are inside root are considered; this is useful when using MutationObserver, which in certain situations can track mutations outside of its root node

Returns:

true if DOM is different from how it started

Type: 
boolean

range(rootopt) → (nullable) {BoundaryRange}

Get a BoundaryRange indicating bounds of the mutated parts of the DOM. You must call this prior to revert, since reverting resets diff tracking. The range returned is a BoundaryRange, utilizing the node-boundary package. A BoundaryRange is similar to a builtin Range, but uses a different internal representation that makes it robust to mutations. The range can still be used if the DOM is reverted, or additional changes occur within the range.

Parameters:
NameTypeAttributesDescription
rootNode<optional>

If provided, only mutations that are inside root are considered; this is useful when using MutationObserver, which in certain situations can track mutations outside of its root node

Throws:

If a root is not specified and mutations affect disconnected DOM trees, there would be multiple disconnected ranges for the mutations; an error is thrown in this case.

Node movements to an "orphaned" DOM are not included in the range, so will not generate this error; examples being a node that is newly added (no prior DOM), or a node is removed (no current DOM). In the case of an error, specify root parameter, which could simply be the document of interest.

Returns:

Returns null if the DOM is not mutated (see mutated). The range can be collapsed, which indicates nodes have been removed at that position. The range is exclusive normalized so that the range bounds are not affected by any mutations inside the range (see BoundaryRange.normalize documentation).

Type: 
BoundaryRange

record(r)

Add the changes indicated by a MutationRecord. Note for attributes and characterData records, you need to include the old value for diffing to function correctly.

Parameters:
NameTypeDescription
rMutationRecord

the record to add

revert(custom_setopt)

Revert the DOM to its original state. This also produces the effects of clear. As noted in clear you may wish to reattach a corresponding MutationObserver.

Parameters:
NameTypeAttributesDescription
custom_setMutationDiff~customSetCbk<optional>

A callback to set the mutated value for custom properties. This is used for any properties modified from custom. If not provided, these properties are not reverted.

synchronize()

Signals that all mutations have been recorded and the view of the DOM given to MutationDiff is up-to-date with the current DOM. This would be the case after MutationObserver.takeRecords() has been called, for example. This allows us to release some cached information about data/attributes/properties. This also can resolves untracked add mutations, which allows DOM trees disconnected from the root to be reverted correctly.

Type Definitions

customGetCbk(node, key) → {*}

For use with MutationDiff#diff

Parameters:
NameTypeDescription
nodeNode

the node whose custom property value we need to fetch

key

the custom property key whose value we want to fetch

Returns:

the custom property's current value

Type: 
*

customSetCbk(node, key, value)

For use with MutationDiff#revert

Parameters:
NameTypeDescription
nodeNode

the node whose custom property we need to set

key*

the custom property key whose value we want to set

value*

the value to set

Diff

The output format returned by MutationDiff#diff

Type:
  • Object
Properties
NameTypeAttributesDescription
dataMutationDiff~DiffProperty<optional>

The diff for a CharacterData's text content. Only present if the DATA flag was included. Will be missing if there were no data changes.

attributeObject.<string, MutationDiff~DiffProperty><optional>

A mapping of attribute names to their diff. Only present if the ATTRIBUTE flag was included. Will be missing if there were no attribute changes

customMap.<*, MutationDiff~DiffProperty><optional>

A mapping of custom properties to their diff. Only present if the CUSTOM flag was included. Will be missing if there were no custom property changes

childrenMutationDiff~DiffChildren<optional>

The diff for a node addition, removal, or movement. Only present if the CHILDREN flag was included. Will be missing if the node's position is the same

DiffChildren

Gives the diff for a node addition, removal, or movement; for use inside MutationDiff~Diff

Type:
  • Object
Properties
NameTypeAttributesDescription
originalMutationDiff~DiffPosition<optional>

The node's original position. Only present if the ORIGINAL flag was included.

mutatedMutationDiff~DiffPosition<optional>

The node's mutated position. Only present if the MUTATED flag was included.

DiffPosition

Gives a node's position in the DOM; for use inside MutationDiff~DiffChildren and yielded from diff_grouped_children.

Type:
  • Object
Properties
NameTypeAttributesDescription
parentNode<optional>
<nullable>

The node's parentNode. If the node was/is not present in the DOM at this point, the parent property will be missing (e.g. the node was removed or newly inserted). This will only be null when output from diff_grouped_children, and include_removed was set to true; a null parent indicates a removed node.

nextNode<optional>
<nullable>

The node's nextSibling. Will be missing if parent is not given. If parent is given, it could be missing if the sibling is unknown, in which case you should call synchronize if you want to know the sibling.

prevNode<optional>
<nullable>

The node's previousSibling. Will be missing if parent is not given. If parent is given, it could be missing if the sibling is unknown, in which case you should call synchronize if you want to know the sibling.

nodesArray.<Node><optional>

An ordered list of adjacent nodes that the position is defined for. The prev and next properties, if present, are the siblings of the first and last nodes of the list respectively. This is only present when yielded from diff_grouped_children

DiffProperty

Gives the diff for a data, attribute, or custom property change; for use inside MutationDiff~Diff

Type:
  • Object
Properties
NameTypeAttributesDescription
original*<optional>

The original value. Only present if the ORIGINAL flag was included

mutated*<optional>

The mutated value. Only present if the MUTATED flag was included. Note that mutated values are not stored internally by MutationDiff, so the current value will be queried from the DOM (or using MutationDiff~customGetCbk for custom properties).