MathJax icon indicating copy to clipboard operation
MathJax copied to clipboard

MathJax 4: startup.promise resolves before DOM has time to update

Open salbeira opened this issue 2 years ago • 7 comments

Issue Summary

Using startup.promise.then() to do post-typesetting adjustments to DOM elements, we mentioned that in MathJax 4 our document.querySelector()s of mjx elements retrieve no elements. Sometimes though, after several random reloads trying to find our error, we actually do get them. This lead us to believe that the promise resolves before the elements are added to the DOM or rather that the promise resolves before the DOM has had time to mention the update, as turning our post-typeset callbacks into renderActions actually tells us that the elements we are interested in are indeed part of the DOM. It feels like the querySelectors should be able to find the items, but they do not. It looks like the promise resolving our callback happens before the actual typeset is complete.

Steps to Reproduce:

  1. Add a callback to typeset.promise via then()
  2. Call something akin to document.querySelectorAll('mjx-container svg g[data-mml-node="mtable"]:first-of-type > g[data-mml-node="mtr"]')
  3. Get no results. Sometimes.

Technical details:

  • MathJax Version: 4.0
  • Client OS: Any (tested on Windows and Mac)
  • Browser: Chrome, Firefox version 119

I am using a MathJax configuration that is irrelevant to the issue.

and loading MathJax via

<script src="/self/hosted/mathjax4/tex-svg.js"></script>

Supporting information:

Here you have a minimal page you can test the issue with:

<!DOCTYPE html>
<html>
  <head>
    <title>Math Test</title>
    <script>

function turnRed() {
  console.log("Turn Red"); // called from startup.promise ... nothing happens
  for(const row of document.querySelectorAll('g[data-mml-node="mtable"] > g[data-mml-node="mtr"]')) {
    row.style.color = "red";
  }
}

window.MathJax = {
  startup: {
    ready: () => {
      window.MathJax.startup.defaultReady();
      window.MathJax.startup.promise.then(() => {
        turnRed();
      });
    }
  }
}
    </script>
    <script defer type="text/javascript" id="MathJax-script" defer src="/support/vendor/mathjax/tex-svg.js"></script>
  </head>
  <body>
    <h1>MathJax Testpage</h1>
    <button onclick="turnRed()">Turn Red</button>
    <span>$$\begin{eqnarray*}
  a &=& b \\
  a^2 &=& ab \\
  2a^2 &=& a^2 + ab \\
  2a^2-2ab &=& a^2 - ab \\
  2a(a-b) &=& a (a-b) \\
  2a &=& a \\
  2 &=& 1
  \end{eqnarray*}$$</span>
  </body>
</html>

What I expect to happen?

turnRed gets called after typesetting and turn each row of the equation red

What happens?

Nothing happens after typesetting. Pressing the "Turn Red" button after the page loaded and a bit of time passed does work though. Changing the callback from turnRed(); to setTimeout(turnRed, 1); also works.

salbeira avatar Nov 14 '23 11:11 salbeira

When I substitute https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js for the source for MathJax in your example document, it works as expected for me (the math turns red without any other action.) This is in Firefox, Chrome, and Safari on MacOS.

Can you try that URL to see if that works for you?

dpvc avatar Nov 14 '23 23:11 dpvc

Yes it works with Mathjax 3. It does not work in Mathjax 4.

Try with https://cdn.jsdelivr.net/npm/[email protected]/tex-svg.js. It does not work.

salbeira avatar Nov 14 '23 23:11 salbeira

OOPS, sorry. Working on answering too many different issues!

It turns out that this is the same issue as #3130: the MathJax.startup.defaultPageReady() function mishandles one of the promises, and that leads to the MathJax.startup.promise being resolved too early. One solution would be to use

window.MathJax = {
  startup: {
    ready: () => {
      window.MathJax.startup.defaultReady();
      window.MathJax.startup.promise.then(() => {
        turnRed();
      });
    },
    pageReady() {
      const CONFIG = MathJax.config.startup;
      const output = MathJax.config.output;
      return (CONFIG.loadAllFontFiles && output.font ? output.font.loadDynamicFiles() : Promise.resolve())
        .then(CONFIG.typeset && MathJax.typesetPromise ?
              () => MathJax.startup.typesetPromise(CONFIG.elements) : Promise.resolve());
    }
  }
};

as the configuration (this in-lines the correct `defaultPageReady()` function). I've already made a PR for the correction that resolve this.

dpvc avatar Nov 15 '23 00:11 dpvc

Thanks for the update. I guess we will just wait until you fixed this in an actual release instead of working around it ourselves just to remove the workaround at the next best moment.

salbeira avatar Nov 15 '23 13:11 salbeira

OK, very good. We should have beta.5 soon and hope to have the official 4.0 release by the end of the year.

dpvc avatar Nov 15 '23 14:11 dpvc

Is there any update on the roadmap? We are inclined to go with the workaround and stick with beta.4. Our goal is to decide upon a mathjax version before the end of the month.

salbeira avatar Feb 05 '24 12:02 salbeira

As you can see, the release didn't happen as we had hoped. The updates to the expression explorer (that would lead to the beta.5 release) have taken longer than expected, and have push everything back. It is likely to still be several months before the official 4.0.

dpvc avatar Feb 06 '24 14:02 dpvc