Repository: sphinx-doc/sphinx

Issue:
Current mathjax loading on per-page basis breaks math in table of contents
### Describe the bug

When a page doesn't use mathjax (:math:) it doesn't load mathjax. This breaks titles in the table of contents that do use math. 

### How to Reproduce

- perform a sphinx-quickstart
- add 'sphinx.ext.mathjax' to extensions
- create otherpage.rst
```
:math:`1+1=\alpha`
==================

Test page with math in title.
```

add otherpage to toctree of index.rst.
- make html

### Expected behavior

Math is properly rendered in table of contents of index.html

### Your project

n/a

### Screenshots

![2021-09-29-133959_1084x394_scrot](https://user-images.githubusercontent.com/1038232/135320739-9258cdaf-6b81-42bd-8d00-9a6e30ca5ec4.png)


### OS

Linux

### Python version

3.9.1

### Sphinx version

3.5.1 and also 4.2.0 (probably also newer)

### Sphinx extensions

sphinx.ext.mathjax

### Extra tools

_No response_

### Additional context

See implementation on per-page loading https://github.com/sphinx-doc/sphinx/issues/6241

Discussion:
picnixz: I'll update the CHANGES as well.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index ad985df22..d908c4b45 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -29,6 +29,11 @@ Bugs fixed
   Patch by Bénédikt Tran.
 * #11666: Skip all hidden directories in ``CatalogRepository.pofiles``.
   Patch by Aryaz Eghbali.
+* #9686: html builder: Fix MathJax lazy loading when equations appear in titles.
+  Patch by Bénédikt Tran.
+* #11483: singlehtml builder: Fix MathJax lazy loading when the index does not
+  contain any math equations.
+  Patch by Bénédikt Tran.
 
 Testing
 -------
diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py
index d283d3f80..d45a35944 100644
--- a/sphinx/domains/math.py
+++ b/sphinx/domains/math.py
@@ -134,11 +134,14 @@ def get_objects(self) -> Iterable[tuple[str, str, str, str, str, int]]:
         return []
 
     def has_equations(self, docname: str | None = None) -> bool:
-        if docname:
-            return self.data['has_equations'].get(docname, False)
-        else:
+        if not docname:
             return any(self.data['has_equations'].values())
 
+        return (
+            self.data['has_equations'].get(docname, False)
+            or any(map(self.has_equations, self.env.toctree_includes.get(docname, ())))
+        )
+
 
 def setup(app: Sphinx) -> dict[str, Any]:
     app.add_domain(MathDomain)


Test patch:
diff --git a/tests/roots/test-ext-math-include/conf.py b/tests/roots/test-ext-math-include/conf.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/roots/test-ext-math-include/included.rst b/tests/roots/test-ext-math-include/included.rst
new file mode 100644
index 000000000..7fb746ecb
--- /dev/null
+++ b/tests/roots/test-ext-math-include/included.rst
@@ -0,0 +1,6 @@
+Title
+=====
+
+Some file including some maths.
+
+.. include:: math.rst
\ No newline at end of file
diff --git a/tests/roots/test-ext-math-include/index.rst b/tests/roots/test-ext-math-include/index.rst
new file mode 100644
index 000000000..cc9533855
--- /dev/null
+++ b/tests/roots/test-ext-math-include/index.rst
@@ -0,0 +1,7 @@
+Test Math
+=========
+
+.. toctree::
+   :numbered: 1
+
+   included
diff --git a/tests/roots/test-ext-math-include/math.rst b/tests/roots/test-ext-math-include/math.rst
new file mode 100644
index 000000000..a4266d03f
--- /dev/null
+++ b/tests/roots/test-ext-math-include/math.rst
@@ -0,0 +1,4 @@
+:math:`1 + 1 = 2`
+=================
+
+Lorem ipsum.
\ No newline at end of file
diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py
index d5331f839..2d3b58246 100644
--- a/tests/test_ext_math.py
+++ b/tests/test_ext_math.py
@@ -343,3 +343,47 @@ def test_mathjax_is_installed_if_no_equations_when_forced(app, status, warning):
 
     content = (app.outdir / 'nomath.html').read_text(encoding='utf8')
     assert MATHJAX_URL in content
+
+
+@pytest.mark.sphinx('html', testroot='ext-math-include',
+                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
+def test_mathjax_is_installed_if_included_file_has_equations(app):
+    app.builder.build_all()
+
+    # no real equations at the rst level, but includes "included"
+    content = (app.outdir / 'index.html').read_text(encoding='utf8')
+    assert MATHJAX_URL in content
+
+    # no real equations at the rst level, but includes "math.rst"
+    content = (app.outdir / 'included.html').read_text(encoding='utf8')
+    assert MATHJAX_URL in content
+
+    content = (app.outdir / 'math.html').read_text(encoding='utf8')
+    assert MATHJAX_URL in content
+
+
+@pytest.mark.sphinx('singlehtml', testroot='ext-math',
+                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
+def test_mathjax_is_installed_only_if_document_having_math_singlehtml(app):
+    app.builder.build_all()
+
+    content = (app.outdir / 'index.html').read_text(encoding='utf8')
+    assert MATHJAX_URL in content
+
+
+@pytest.mark.sphinx('singlehtml', testroot='basic',
+                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
+def test_mathjax_is_not_installed_if_no_equations_singlehtml(app):
+    app.builder.build_all()
+
+    content = (app.outdir / 'index.html').read_text(encoding='utf8')
+    assert 'MathJax.js' not in content
+
+
+@pytest.mark.sphinx('singlehtml', testroot='ext-math-include',
+                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
+def test_mathjax_is_installed_if_included_file_has_equations_singlehtml(app):
+    app.builder.build_all()
+
+    content = (app.outdir / 'index.html').read_text(encoding='utf8')
+    assert MATHJAX_URL in content