Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Idiomorph is a javascript library for morphing one DOM tree to another. It is inspired by other libraries that pioneered this functionality:
Both morphdom and nanomorph use the id
property of a node to match up elements within a given set of sibling nodes. When
an id match is found, the existing element is not removed from the DOM, but is instead morphed in place to the new content.
This preserves the node in the DOM, and allows state (such as focus) to be retained.
However, in both these algorithms, the structure of the children of sibling nodes is not considered when morphing two nodes: only the ids of the nodes are considered. This is due to performance: it is not feasible to recurse through all the children of siblings when matching things up.
Idiomorph takes a different approach: before node-matching occurs, both the new content and the old content are processed to create id sets, a mapping of elements to a set of all ids found within that element. That is, the set of all ids in all children of the element, plus the element's id, if any.
Id sets can be computed relatively efficiently via a query selector + a bottom up algorithm.
Given an id set, you can now adopt a broader sense of "matching" than simply using id matching: if the intersection between the id sets of element 1 and element 2 is non-empty, they match. This allows Idiomorph to relatively quickly match elements based on structural information from children, who contribute to a parent's id set, which allows for better overall matching when compared with simple id-based matching.
Idiomorph is a small (1.7k min/gz'd), dependency free JavaScript library, and can be installed via NPM or your favorite
dependency management system under the Idiomorph
dependency name. You can also include it via a CDN like unpkg to
load it directly in a browser:
<script src="https://unpkg.com/idiomorph"></script>
Or you can download the source to your local project.
Idiomorph has a very simple usage:
Idiomorph.morph(existingNode, newNode);
This will morph the existingNode to have the same structure as the newNode. Note that this is a destructive operation with respect to both the existingNode and the newNode.
You can also pass string content in:
Idiomorph.morph(existingNode, "<div>New Content</div>");
And it will be parsed and merged into the new content.
If you wish to target the innerHTML
rather than the outerHTML
of the content, you can pass in a morphStyle
in a third config argument:
Idiomorph.morph(existingNode, "<div>New Content</div>", {morphStyle:'innerHTML'});
This will replace the inner content of the existing node with the new content.
Idiomorph was created to integrate with htmx and can be used as a swapping mechanism by including
the Idiomorph-ext
file in your HTML:
<script src="https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js"></script>
<div hx-ext="morph">
<button hx-get="/example" hx-swap="morph:innerHTML">
Morph My Inner HTML
</button>
<button hx-get="/example" hx-swap="morph:outerHTML">
Morph My Outer HTML
</button>
<button hx-get="/example" hx-swap="morph">
Morph My Outer HTML
</button>
</div>
Idiomorph is not designed to be as fast as either morphdom or nanomorph. Rather, its goals are:
Performance is a consideration, but better matching is the reason Idiomorph was created. Initial tests indicate that it is approximately equal to 10% slower than morphdom for large DOM morphs, and equal to or faster than morphdom for smaller morphs.
Here is a simple example of some HTML in which Idiomorph does a better job of matching up than morphdom:
Initial HTML
<div>
<div>
<p id="p1">A</p>
</div>
<div>
<p id="p2">B</p>
</div>
</div>
Final HTML
<div>
<div>
<p id="p2">B</p>
</div>
<div>
<p id="p1">A</p>
</div>
</div>
Here we have a common situation: a parent div, with children divs and grand-children divs that have ids on them. This is a common situation when laying out code in HTML: parent divs often do not have ids on them (rather they have classes, for layout reasons) and the "leaf" nodes have ids associated with them.
Given this example, morphdom will detach both #p1 and #p2 from the DOM because, when it is considering the order of the children, it does not see that the #p2 grandchild is now within the first child.
Idiomorph, on the other hand, has an id set for the (id-less) children, which includes the ids of the grandchildren. Therefore, it is able to detect the fact that the #p2 grandchild is now a child of the first id-less child. Because of this information it is able to only move/detach one grandchild node, #p1. (This is unavoidable, since they changed order)
So, you can see, by computing id sets for nodes, idiomoroph is able to achieve better DOM matching, with fewer node detachments.
You can see a practical demo of Idiomorph out-performing morphdom (with respect to DOM stability, not performance) here:
https://github.com/bigskysoftware/Idiomorph/blob/main/test/demo/video.html
For both algorithms, this HTML:
<div>
<div>
<h3>Above...</h3>
</div>
<div>
<iframe id="video" width="422" height="240" src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="Rick Astley - Never Gonna Give You Up (Official Music Video)" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
</div>
is moprhed into this HTML:
<div>
<div>
<iframe id="video" width="422" height="240" src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="Rick Astley - Never Gonna Give You Up (Official Music Video)" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
<div>
<h3>Below...</h3>
</div>
</div>
Note that the iframe has an id on it, but the first-level divs do not have ids on them. This means that morphdom is unable to tell that the video element has moved up, and the first div should be discarded, rather than morphed into, to preserve the video element.
Idiomorph, however, has an id-set for the top level divs, which includes the id of the embedded child, and can see that the video has moved to be a child of the first element in the top level children, so it correctly discards the first div and merges the video content with the second node.
You can see visually that idiomoroph is able to keep the video running because of this, whereas morphdom is not:
To keep things stable with morphdom, you would need to add ids to at least one of the top level divs.
FAQs
an id-based DOM morphing library
The npm package idiomorph receives a total of 22,154 weekly downloads. As such, idiomorph popularity was classified as popular.
We found that idiomorph demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.