MathJax typesets removed DOM elements causing measureTextNodeWithCache pollution
Issue Summary
In MathJax 4.0.0.beta7, due to some asynchronous execution issues, I sometimes call typeset on the element after the element is removed.
Some of my CJK characters are folded together. I looked at the source code and it was roughly due to the following reasons.
When Mathjax (svg) encounters a cjk character, it calls jax.unknownText to create a text element, and then calls jax.measureTextNodeWithCache to calculate the text width (or read the cache)
If the dom is removed, the em/ex value calculated by Metrics will be NaN, which will cause the element created by jax.unknownText to get fontsize:NaNpx, and measureTextNodeWithCache will get a value close to 0.
So far, everything is fine, because the rendering result will not actually be on window, but measureTextNodeWithCache will record the calculation result in a persistent cache, which will eventually cause all subsequent width calculations of this CJK character to fail.
Steps to Reproduce:
const mathjax = await getMathJax();
const div1 = document.createElement('div');
div1.innerText = '\\(\\ce{ZnCO3 \\xlongequal{高温} ZnO + CO2 ^}\\)';
document.body.appendChild(div1);
const p = mathjax.typesetPromise([div1]);
div1.remove();
await p;
const div2 = document.createElement('div');
div2.innerText = '\\(\\ce{ZnCO3 \\xlongequal{高温} ZnO + CO2 ^}\\)';
document.body.appendChild(div2);
mathjax.typesetPromise([div2]);
Technical details:
- MathJax Version: 4.0.0.beta7
- Client OS: (e.g., Mac OS X 10.8.4)
- Browser: (e.g., Chrome 29.0.1547.57)
Thanks for the report.
Of course, it is never a good idea to alter the DOM while MathJax is trying to typeset it, so that should really be fixed in your code.
In the meantime, you can use the following configuration to work around the issue.
MathJax = {
startup: {
ready() {
const {CHTML} = MathJax._.output.chtml_ts;
const common = CHTML.prototype.__proto__;
Object.assign(common, {
_measureTextNodeWithCache_: common.measureTextNodeWithCache,
measureTextNodeWithCache(text, chars, variant, font = ['', false, false]) {
const bbox = this._measureTextNodeWithCache_(text, chars, variant, font);
if (isNaN(bbox.w)) {
if (variant === '-explicitFont') {
variant = [font[0], font[1] ? 'T' : 'F', font[2] ? 'T' : 'F', ''].join('-');
}
this.unknownCache.get(variant).delete(chars);
bbox.w = 0;
}
return bbox;
}
});
MathJax.startup.defaultReady();
}
}
}
This will prevent the size data from being stored if the width could not be determined.
I will make a PR for v4 that does something similar.