MathJax icon indicating copy to clipboard operation
MathJax copied to clipboard

Error "s.tagName is undefined" when clicking text node in mjx-break

Open devkat opened this issue 3 months ago • 8 comments

Issue Summary

Clicking on text nodes in SVG causes an exception.

Steps to Reproduce:

  1. Render the formula D_i (0 \leq i < n)
  2. Click on the whitespace between the 0 and the \leq character
  3. An exception is thrown: TypeError: can't access property "toLowerCase", s.tagName is undefined

The exception is thrown here: https://github.com/mathjax/MathJax-src/blob/1a2ef74c0ac0620e7b8de46402c9dce3b95ade52/ts/a11y/explorer/KeyExplorer.ts#L1420

This type assertion casts text nodes to elements:

const nodes = Array.from(clicked.childNodes) as HTMLElement[];

See attached screenshot for the whitespace node in the DOM (inside <mjx-break>).

Technical details:

  • MathJax Version: 4.4.0
  • Client OS: macOS 15.6.1 (24G90)
  • Browser: Firefox 143.0.4 (aarch64)

I am using the following MathJax configuration:

window.MathJax = {
  startup: {
    // Typesetting will be triggered from the React components
    typeset: false,

    pageReady() {
      resolveMathJaxPromise(mathJax);
      return MathJax.startup.defaultPageReady();
    },

    // getComputedStyle returns an empty font size if the node is detached from the DOM
    // https://codeexpert.myjetbrains.com/youtrack/issue/cx-3811/MathJax-crash-in-Safari-26
    // FIXME: remove this once https://github.com/mathjax/MathJax/issues/3458 is fixed
    ready() {
      MathJax.startup.defaultReady();
      MathJax.startup.document.adaptor.fontSize = function (node: Node) {
        const { fontSize }: CSSStyleDeclaration = this.window.getComputedStyle(node);
        return fontSize == null || string.isBlank(fontSize) ? 16 : parseFloat(fontSize);
      };
    },
  },
  loader: {
    load: ['ui/safe'],
    source: {
      '[tex]/amsCd': '[tex]/amscd',
    },
  },
  output: {
    fontPath: `${mathJaxPath}/mathjax-newcm-font`,
  },
  options: {
    safeOptions: {
      allow: {
        //
        //  Values can be "all", "safe", or "none"
        //
        URLs: 'none', // 'none' to prevent cookie theft through external URLs
        classes: 'safe', // safe start with mjx- (can be set by pattern below)
        cssIDs: 'safe', // safe start with mjx- (can be set by pattern below)
        styles: 'safe', // safe are in safeStyles below
      },
      //
      //  Which styles are allowed
      //
      safeStyles: {
        color: true,
        backgroundColor: true,
        border: true,
        cursor: true,
        margin: true,
        padding: true,
        textShadow: true,
        fontFamily: true,
        fontSize: true,
        fontStyle: true,
        fontWeight: true,
        opacity: true,
        outline: true,
      },
      lengthMax: 3, // Largest padding/border/margin, etc. in em's
      scriptsizemultiplierRange: [0.6, 1], // Valid range for scriptsizemultiplier
      scriptlevelRange: [-2, 2], // Valid range for scriptlevel
      classPattern: /^mjx-[-a-zA-Z0-9_.]+$/, // Pattern for allowed class names
      idPattern: /^mjx-[-a-zA-Z0-9_.]+$/, // Pattern for allowed ids
      dataPattern: /^data-mjx-/, // Pattern for data attributes
    },
  },
  tex: {
    inlineMath: [['$', '$']],
    displayMath: [['$$', '$$']],
    processEscapes: true,
  },
};

and loading MathJax via

http://localhost:3020/mathjax/tex-svg.js
Image Image

devkat avatar Nov 04 '25 07:11 devkat

Interestingly, the online demo (https://www.mathjax.org/#demo) renders empty mjx-break elements:

When $D_i (0 \leq i < n)$, …
Image Image

devkat avatar Nov 04 '25 07:11 devkat

A potential workaround is moving the mjx-break element vertically to preserve the width but prevent it from being detected as click target:

mjx-break {
  position: relative;
  top: -10em;
}

Since MathJax registers the event listeners on mjx-container in the capture phase, there seems to be no possibility to intercept the event (https://github.com/mathjax/MathJax-src/blob/1a2ef74c0ac0620e7b8de46402c9dce3b95ade52/ts/ui/menu/Menu.ts#L1721).

devkat avatar Nov 04 '25 13:11 devkat

Thanks for the report. I will make a PR to fix the problem. In the meantime, you can merge the following configuration into yours:

MathJax = {
  startup: {
    ready() {
      const { SpeechExplorer } = MathJax._.a11y.explorer.KeyExplorer;
      SpeechExplorer.prototype.findClicked = function (node, x, y) {
        const icon = this.document.infoIcon;
        if (icon === node || icon.contains(node)) return icon;
        if (this.node.getAttribute('jax') !== 'SVG') return node.closest(nav);
        let found = null;
        let clicked = this.node;
        while (clicked) {
          if (clicked.matches('[data-speech-node]')) found = clicked;
          const nodes = Array.from(clicked.childNodes);
          clicked = null;
          for (const child of nodes) {
            if (child !== this.speech && child !== this.img &&
                child.tagName && child.tagName.toLowerCase() !== 'rect') {
              const { left, right, top, bottom } = child.getBoundingClientRect();
              if (left <= x && x <= right && top <= y && y <= bottom) {
                clicked = child;
                break;
              }
            }
          }
        }
        return found;
      };
      MathJax.startup.defaultReady();
    }
  }
};

It tests that child.tagName exists before using it.

dpvc avatar Nov 04 '25 13:11 dpvc

I've made the PR for it.

dpvc avatar Nov 04 '25 13:11 dpvc

@dpvc, thanks a lot! For the moment I have incorporated the changes from your PR using patch-package.

devkat avatar Nov 04 '25 14:11 devkat

@dpvc, by the way, do you know why the online demo (https://www.mathjax.org/#demo) doesn't render the spaces inside mjx-break in SVG mode? Maybe this should be looked at as well.

devkat avatar Nov 04 '25 14:11 devkat

do you know why the online demo doesn't render the spaces inside mjx-break in SVG mode?

There is a CSP issue: the SVG font for the space is in a data: URL and isn't currently being allowed. I will need to adjust the CSP for the site.

dpvc avatar Nov 04 '25 14:11 dpvc

I fixed the CSP on www.mathjax.org, so you should see the proper spacing in SVG output now.

dpvc avatar Nov 05 '25 14:11 dpvc