You might have noticed that recent versions improved JShelter performance. This blog post explains the improvements in more detail and contains graphs. The improvements are based on the bachelor thesis of Martin Zmitko. If you are interested in this topic, you will find more information in the thesis. We thank Martin for his work and his proposals.
Starting from 0.5, NSCL solved the reliable code injection. The preferred and fast
solution is to inject the configuration in the
BeforeNavigate event handler. However, there is a race
condition between the
BeforeNavigate event and
document_start phase of the page load. If the
script detects that the configuration is not available during the
document_start, it initiates a
synchronous request to retrieve the configuration before page scripts start running.
However, Martin realized that a synchronous request takes a long time. Moreover, he confirmed our old observations that the synchronous request is needed very often. The time needed to process the configuration increases linearly with the size of the configuration. JShelter used to inject 572kB of code in the default configuration. By shifting the code generation process back to content scripts, we decreased the configuration size to 21.2kB.
During the work, we also optimized the code-generating process and eliminated duplicates in the code as well as Firefox-specific code in Chromium-based browsers.
As you probably know, the anti-fingerprinting code modifies the results of some APIs with
little lies. However, that approach is performance-heavy for some APIs. The most critical are APIs
that read from canvas (
AudioBuffer.getChannelData. For example,
getChannelData passes a reference to the underlying buffer, so the browser does not
need to do any computation. But JShelter needs to copy each item, determine how to apply the lies
(ensuring consistent lies to the same data) and modify selected items.
Martin discovered that the JShelter modifications to
can benefit from a different iterator. More importantly, Martin proposed to translate the code to
WebAssembly, which runs much faster.
Fingerprint detector collects information on each call of the APIs that are often misused for fingerprinting. Martin discovered that some serializations performed during its operations are not really needed.
Martin also implemented several other performance improvements, some aiming at NSCL and not only
SubtleCrypto implementation is faster.
First, let us have a look at Firefox and the improvements in 2D Canvas in
that the y-axis is logarithmic):
As expected, the optimized implementation is slower than the original because it needs much more work. Even so, the performance hit is several magnitudes lower than the hit in 0.12.2.
Now, let us have a look the improvements in 3D Canvas in
readPixels() (this time, we show the
graph with the linear y-axis, the shape of the points depicting the performance is similar for
2D and 3D canvas):
See that the original implementation quickly leaves the plotted range. Its performance hit was more significant than the 2D version while starting from 0.14, the performance hit of 2D and 3D canvases are comparable.
Firefox implementation of
getChannelData has a negligible running time (almost 0). The following figure shows that the little lies are computed much quicker (about two orders of magnitude), but the impact is still significant. Note that the y-axis is again logarithmic.
Martin also developed performance tests based on Google Lighthouse that run in Chrome. We tested on 46 pages from the 100 of Tranco and give the performance score below. The performance score approximates how users perceive the loading speed of the visited page. The 25th percentile of all pages should receive a score of 50.
The average performance score of all tested pages was 66 without JShelter. When tested with JShelter 0.12.2, the score dropped to 62.5. The average of all tested pages raised to 64 with JShelter 0.15.1. The performance score was the same or better in JShelter 0.15.1 compared to 0.12.2 on 33 pages. It increased on 18 pages.