Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MathJax v4 error: this.parent(...) is null #3130

Open
hbghlyj opened this issue Nov 12, 2023 · 1 comment
Open

MathJax v4 error: this.parent(...) is null #3130

hbghlyj opened this issue Nov 12, 2023 · 1 comment
Labels
Accepted Issue has been reproduced by MathJax team Code Example Contains an illustrative code example, solution, or work-around Merged Merged into develop branch Test Needed v4

Comments

@hbghlyj
Copy link

hbghlyj commented Nov 12, 2023

I'm loading MathJax in a simple HTML webpage, via jsdelivr

<!DOCTYPE html>
<html>
<head>
  <script>window.MathJax = {
      startup: {
        pageReady: function () {
          MathJax.typesetPromise([document.body]);
          return MathJax.startup.defaultPageReady();
        }
      }
    };
  </script>
  <script src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js"></script>
</head>

<body>
  <div>\(\mathbb{Q}\)
  </div>
</body>

</html>

Firefox throws error at tex-mml-chtml.js line 1 column 41740:

image

Uncaught (in promise) TypeError: this.parent(...) is null
    replace https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    updateDocument https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    updateDocument https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    updateDocument https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    updateDocument https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    updateDocument https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    methodActions https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    renderDoc https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    render https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    n https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    t https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    t https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    promise callback*t https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    t https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    promise callback*t https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    Mn https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    n https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    defaultPageReady https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    pageReady file:///this file.htm:9
    defaultReady https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    promise callback*e.defaultReady https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    defaultReady https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    <anonymous> https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    promise callback* https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    <anonymous> https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    loadFont https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    <anonymous> https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    <anonymous> https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    <anonymous> https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1
    <anonymous> https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.4/tex-mml-chtml.js:1

Chrome also throws error: Uncaught (in promise) TypeError: Cannot read properties of null (reading 'replaceChild')

image
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'replaceChild')

@hbghlyj hbghlyj changed the title Uncaught (in promise) TypeError: this.parent(...) is null MathJax v4 error: this.parent(...) is null Nov 12, 2023
@dpvc
Copy link
Member

dpvc commented Nov 13, 2023

There are several things happening, here, and the main issue is an error with how some promises are being handled internally. There is also a difference between v3 and v4 in how this is handled.

It is important not to have two separate typeset operations going on simultaneously, because if the first one requires dynamically loaded code, it will pause and the second one will run during that pause, which can cause problems (for example, automatically assigned equation numbers can get out of order, or other global values can clash).

Because MathJax.typesetPromise() is performed within a promise, the code that follows it is performed before the typesetting starts. But MathJax.startup.defaultPageReady() also does a promise-based typeset operation (the initial typesetting of the page that is the default for MathJax). That means there are two typeset operations queued up, which can cause the problems I described above.

In v4, with its new larger font coverage, not all the font data is loaded initially, and the use of some characters causes that data to be loaded dynamically. The double-struck characters (like \mathbb{Q}) are ones that require extra data to be loaded. So your expression causes the bad situation that I mentioned where the first typeset operation must pause, and the second begins before the first is done. Although v3 doesn't have dynamic font-loading, you can cause this problem in v3 by using a dynamicaly loaded TeX command, like \cancel, so \cancel{x} instead of \mathbb{Q} would cause the same problem in v3. These issues are discussed in the documentation.

What's happening in your case is that the first typeset identifies \mathbb{Q} as the expression to typeset, but has to wait for the data for double-struck characters to be downloaded, so the second typeset starts, and it also finds the \mathbb{Q} (since it has not yet been replaced by its typeset version) and starts typesetting it. But it also needs to wait for the double-struck data, and so both typesets are paused. When the data are available, the first continues, finished the typesetting and replaces the initial LaTeX text with the typeset version. Then the second typesetting finishes and tries to replace the original text by its typeset version, but that original text is no longer in the document, so has no parent element, which is what your error message is complaining about.

The documentation linked above suggests using the MathJax.startup.promise to serialize your typeset calls. In v3, you had to do this manually, but in v4, this has been incorporated into the promise-based calls themselves, so they serialize automatically. Because the initial MathJax.startup.promise doesn't resolve until after the initial typesetting pass (that is, not until after the pageReady() function is performed and its promise is resolved), that means your MathJax.typesetPromise() will not be performed until after the MathJax.startup.defaultPageReady() function's promise is resolved. And that means that the MathJax.startup.defaultPageReady() is the first typeset to be performed, and MathJax.typesPromise() is the second one.

Because of the internal serialization in v4, your code should work, though in the reverse order of what you might expect. But there is a bug in MathJax.startup.defaultPageReady() that mishandles one of the promises. This line should be

            () => typesetPromise(CONFIG.elements) :

so that the typesetPromise() is not performed until the then() is triggered. Right now, it is performed immediately when defaultPagePromise() is called. (It took me quite a like two track that one down.)

One work-around for now would be of you to use

      pageReady: function () {
         return MathJax.startup.defaultPageReady().then(() => MathJax.typesetPromise([document.body]));
       }

instead, since the typesetPromise() isn't supposed to be performed until after the MathJax.startup.promise resolves, which isn't until after defaultPageReady() resolves.

Alternatively, you could do

      pageReady: function () {
         MathJax.typesetPromise([document.body]);
         return Promise.resolve();
       }

since the defaultPageReady() is not needed if you are already typesetting the entire document.

It's not clear to me what you are actually trying to accomplish with this pageReady() function, since the default action is to typeset the document body, so your MathJax.typesetPromise() call is redundant. I assume that this is not the actual code you are using, and that you have something else in mind.

If you want to have more precise control over the order in which things are typeset, it is probably best to set startup.typeset to false in the MathJax configuration object, and then make pageReady() do whatever typesetting needs to be done in the order you want it to be. E.g.

    window.MathJax = {
      startup: {
        typeset: false,
        pageReady: function () {
          MathJax.typesetPromise([element1]);
          MathJax.typesetPromise([element2]);
          return MathJax.startup.defaultPageReady();
        }
      }
    };

I will make a PR to fix the problem with the defaultPageReady() function.

@dpvc dpvc added Ready for Development Accepted Issue has been reproduced by MathJax team Test Needed Code Example Contains an illustrative code example, solution, or work-around v4 labels Nov 13, 2023
dpvc added a commit to mathjax/MathJax-src that referenced this issue Nov 13, 2023
dpvc added a commit to mathjax/MathJax-src that referenced this issue Nov 13, 2023
dpvc added a commit to mathjax/MathJax-src that referenced this issue Nov 20, 2023
Proper handling of typesetPromise in defaultPageReady. (mathjax/MathJax#3130)
@dpvc dpvc added Merged Merged into develop branch and removed Ready for Review labels Nov 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Accepted Issue has been reproduced by MathJax team Code Example Contains an illustrative code example, solution, or work-around Merged Merged into develop branch Test Needed v4
Projects
None yet
Development

No branches or pull requests

2 participants