mathjax / MathJax

Beautiful and accessible math in all browsers
http://www.mathjax.org/
Apache License 2.0
10.04k stars 1.16k forks source link

Crash in 4 beta 6 that wasn't in beta 4 #3253

Open NSoiffer opened 2 weeks ago

NSoiffer commented 2 weeks ago

Issue Summary

Switching

    <script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js"></script>

to

    <script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.6/tex-mml-chtml.js"></script>

caused a crash in MathJax in speech().

This happens in both Firefox and Chrome on Windows 10.

I don't want to the accessibility features on because the page that this is in replaces them with my own math accessibility. If there is some new settings I should use, let me know. Potentially, that setting should not have been used in beta 4, but the speech code didn't cause a crash before.

Steps to Reproduce:

Apologies, I don't have an easy way for you to replicate my code (I include the relevant portions below)

Technical details:

I am using the following MathJax configuration:

    <!-- MathJax-related stuff -->
    <script>
    MathJax = {
      loader: {load: ['input/tex', '[tex]/mhchem', 'input/asciimath', '[mml]/mml3']},
      tex: {packages: {'[+]': ['mhchem']}},
      options: {
        enableMenu: false, // interferes with navigation
        enableEnrichment: false,
        enableExplorer: false,
      }
    };

    // Convert MathML, TeX (properly delimitated), and ASCIIMath (properly delimitated)
    function ConvertToMathML(math_str, math_format) {
      MathJax.startup.defaultReady();
      let options = {display: true};
      let mathml;
      try {
        if (math_format == 'ASCIIMath') {
          mathml = MathJax.asciimath2mml(math_str, options);
        } else if (math_format == 'TeX') {
          mathml = MathJax.tex2mml(math_str, options);
        } else {  // should be "MathML"
          mathml = MathJax.mathml2mml(math_str, options);
        };
      } catch (e) {
        console.error("MathJax conversion error: ", e);
        mathml = "<math><merror><mtext>MathJax conversion error</mtext></merror></math>"
      }
      console.log("ConvertToMathML:\n" + mathml.toString());
      return mathml;
    }

    // Get the MathJax version of the MathML
    function ConvertToCHTML(mathml) {
      MathJax.startup.defaultReady();
      return MathJax.mathml2chtml(mathml);
    }

  <script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.6/tex-mml-chtml.js"></script>

Supporting information:

Here's the stack:

Uncaught TypeError: Cannot read properties of null (reading 'speechRegion')
    at Object.speech (tex-mml-chtml.js:1:916727)
    at ux.init (tex-mml-chtml.js:1:918087)
    at Bn.explorable (tex-mml-chtml.js:1:919848)
    at Object.renderMath (tex-mml-chtml.js:1:116056)
    at yn.renderConvert (tex-mml-chtml.js:1:116378)
    at Bn.convert (tex-mml-chtml.js:1:48699)
    at t.convert (tex-mml-chtml.js:1:118212)
    at Li.<computed> [as mathml2chtml] (tex-mml-chtml.js:1:34675)
    at ConvertToCHTML (MathCATDemo/:42:22)
    at imports.wbg.__wbg_ConvertToCHTML_4da9a985d98a3bd1 (index-a98a542f47807695.js:337:25)
dpvc commented 2 weeks ago

The enableEnrichment and enableExplorer options are actually managed by the contextual menu, when its code is loaded (as it is in your case since you are loading tex-mml-chtml.js, which includes the menu code). This is true even if enableMenu is false, as that really only controls whether the menu is added to the individual math expressions in the page. The documentation on all of that could be clearer, and I suppose that when enableMenu is false, the initialization of the menu (which sets the other enable options) could be avoided.

The upshot here is that you haven't actually disabled the enhancement or the explorer, and the message you are getting is due to that. That still shouldn't happen, but the speech and accessibility features have been significantly updated in beta.6, and it looks like something has gone wrong with that.

In the meantime, you can done one of two things. The first would be to use

    MathJax = {
      loader: {load: ['input/tex', '[tex]/mhchem', 'input/asciimath', '[mml]/mml3']},
      tex: {packages: {'[+]': ['mhchem']}},
      options: {
        enableMenu: false,
        menuOptions: {settings: {enrich: false}}
      }
    };

to disable enrichment (which also should disable the speech production and explorer).

Alternatively, since you are already loading nearly all the needed components anyway, you could just use

    MathJax = {
      loader: {load: ['input/tex', '[tex]/mhchem', 'input/mml', 'input/asciimath', '[mml]/mml3', 'output/chtml']},
      tex: {packages: {'[+]': ['mhchem']}}
    };

and then load

<script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.6/startup.js"></script>

instead of tex-mml-chtml.js, as that will avoid loading the menu, enrichment, and explorer code entirely, so there is not need to disable it.

I also notice you are calling MathJax.startup.defaultReady() in several locations. As I mentioned to you in a previous issue tracker, that is not a good idea, as it is only meant to be run within your own MathJax.startup.ready() function. If you are trying to re-instantiate the document and the input and output jax, it would be better to use MathJax.startup.getComponents(), though that will get a new CHTML output jax as well, and that means it will not know about the CSS that has been added to the page previously in order to handle the CHTML output, and since that is updated based on the expressions that you process, many duplicate CSS rules may be added to the page. That can cause potential performance problems if you call getComponents() frequently, as it appears from these snippets that you may be doing. If you only want to reset the TeX input jax, it might be better to do

MathJax.startup.document.inputJax = MathJax.startup.getInputJax();

instead to get fresh instances of the input jax, or even just use

MathJax.texReset()

to clear the labels if that is what you are looking for, though this does not clear macros defined with \newcommand or \def, or that are loaded via \require{} or by auto-loaded extensions.

In any case, it would be best not to use defaultReady() in the way you are using it.

dpvc commented 2 weeks ago

I did some more testing to find out what was causing the error itself in your original code, and figured out what is going on. The MathJax.startup.defaultReady() calls are actually the trigger for this, as they replace the MathJax.startup.document. It turns out that there is some initialization of properties of the document that are used by the speech-generation code, and that occurs during the initial typesetting pass. When you replace that document with a newly instantiated one and immediately call a conversion operation (without having done a page-based typeset call), those properties are not set, and that is leading to the error you get.

My suggestion of replacing the defaultReady() with getComponents() would also have the same problem for the same reason. But the other suggestions should work, namely changing how you disable the explorer and enrichment, or using startup.js rather than tex-mml-chtml.js and loading the components by hand (I left out output/chtml in my original post, but have corrected it above), or only re-instantiating the input jax rather than the complete document.

For the latter, you do need to do just a little bit more than I said above. In particular, you need to initialize the input jax's adaptors and mmlFactory properties to be the ones from the document itself, so you should do

const doc = MathJax.startup.document;
doc.inputJax = MathJax.startup.getInputJax();
doc.inputJax.map(jax => {
  jax.setAdaptor(doc.adaptor);
  jax.setMmlFactory(doc.mmlFactory);
});

to get everything properly set up with new input jax. I had forgotten about that when I wrote my original reply.