Page MenuHomePhabricator

Load all MathJax files from WMF servers
Closed, ResolvedPublic

Details

Related Changes in Gerrit:

Event Timeline

Change #1228601 had a related patch set uploaded (by Physikerwelt; author: Physikerwelt):

[mediawiki/extensions/Math@master] Add all MathJax (remove CDN dependency)

https://gerrit.wikimedia.org/r/1228601

https://gerrit.wikimedia.org/r/1228601 does quite a few things at once. Especially the adjustment of the config is more relevant to T414055. Independent of the question if MathJax is a suitable default mode, I would feel better if we could load the fonts from wmf servers and not from a cdn. That beeing said, also the CHTML config https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Math/+/1228601/7 would do the job, while being more different from the current svg output.

Yeah. I'm fine checking it in. I failed to notice the third-party domain calls in my review for the opt-in mode a few years ago.

To make matters worse, the CSP report-only warnings in Logstash are lost in the noise from user scripts importing e.g. https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js, which in turn ends up importing from cdn.mathjax.org, just like the Math extension. There are (currently) far more pageviews that fetch cdn.mathjax.org due to cdnjs.cloudflare.com, than due to ext.math.mathjax so those warnings were easily dismissed as the result a personal user script or gadget, rather than the Math extension.

There is currently a problem with the Previews. The current wrapper only works with the chtml output but not with the SVG output. Here is a working prototype:

....
        startup: {
		// MathJax creates anchor tags from MathML elements with href attributes.
		// But it does not add the title attributes from these elements
		// that we need for the extension Popups
		ready() {
			const { MML } = window.MathJax._.core.MmlTree.MML;
			MML.a = MML.mrow;
			const { ChtmlWrapper } = window.MathJax._.output.chtml.Wrapper;
			ChtmlWrapper.prototype.handleHref = function ( parents ) {
				if ( !this.node.attributes.hasExplicit( 'href' ) ) {
					return parents;
				}
				const attrs = { href: this.node.attributes.get( 'href' ) };
				if ( this.node.attributes.hasExplicit( 'title' ) ) {
					attrs.title = this.node.attributes.get( 'title' );
				}
				return parents.map(
					( parent ) => this.adaptor.append( parent, this.html( 'a', attrs ) )
				);
			};
			const { SvgWrapper } = window.MathJax._.output.svg.Wrapper;
			SvgWrapper.prototype.handleHref = function ( parents ) {
				const href = this.node.attributes.get( 'href' );
				if ( !href ) {
					return parents;
				}

				const defineOffsetGetter = ( target, key, rectKey ) => {
					if ( typeof target[ key ] !== 'undefined' ) {
						return;
					}
					Object.defineProperty( target, key, {
						get: () => {
							const rect = target.getBoundingClientRect && target.getBoundingClientRect();
							return rect && Number.isFinite( rect[ rectKey ] ) ? rect[ rectKey ] : 0;
						}
					} );
				};
				const attrs = { href };
				if ( this.node.attributes.hasExplicit( 'title' ) ) {
					const title = this.node.attributes.get( 'title' );
					attrs[ 'data-title' ] = title;
					attrs.title = title;
				}
				let index = 0;
				const isEmbellished = this.node.isEmbellished && !this.node.isKind( 'mo' );
				return parents.map( ( parent ) => {
					parent = this.adaptor.append( parent, this.svg( 'a', attrs ) );
					const { h, d, w } = isEmbellished ? this.getOuterBBox() : this.getLineBBox( index );
					const dom = this.dom[ index ];
					const rect = this.svg( 'rect', {
						'data-hitbox': true,
						fill: 'none',
						stroke: 'none',
						'pointer-events': 'all',
						width: this.fixed( w ),
						height: this.fixed( h + d ),
						x: index === 0 || isEmbellished ? this.fixed( -this.dx ) : 0,
						y: this.fixed( -d )
					} );
					this.adaptor.append(
						dom,
						rect
					);
					defineOffsetGetter( parent, 'offsetWidth', 'width' );
					defineOffsetGetter( parent, 'offsetHeight', 'height' );
					index++;
					return parent;
				} );
			};
			window.MathJax.startup.defaultReady();
		},
		// See https://phabricator.wikimedia.org/T375932 and the suggested fix from
		// https://github.com/mathjax/MathJax/issues/3292#issuecomment-3487698042
		// Makes rendering of \matcal look similar to the browsers MathML rendering
		// and the old image rendering.
		// Note that \mathsrc (which is unsupported by texvc) would map to the
		// same unicode chars and thus should not be activated.
		pageReady() {
			const font = window.MathJax.startup.document.outputJax.font;
			Object.assign( font, {
				fontLoadDynamicFile: font.loadDynamicFile,
				async loadDynamicFile( dynamic ) {
					await this.fontLoadDynamicFile( dynamic );
					if ( dynamic.file === 'script' ) {
						await this.fontLoadDynamicFile( this.constructor.dynamicFiles.calligraphic );
						const variant = font.variant;
						const map = { 1: 0x212C, 4: 0x2130, 5: 0x2131, 7: 0x210B, 8: 0x2110, 11: 0x2112, 12: 0x2133, 17: 0x211B };
						window.MathJax.config.remapChars( variant.normal, variant[ '-tex-calligraphic' ], 0x1D49C, map, 'C' );
						window.MathJax.config.remapChars( variant.normal, variant[ '-tex-bold-calligraphic' ], 0x1D4D0, {}, 'CB' );
					}
				}
			} );
			return window.MathJax.startup.defaultPageReady().then( () => {
			} );
		},
		output: 'svg'
	}
``` The code is a bit more complicated because there was a positioning bug of the preview, similar to the one from T381311.

@FrederikHennecke1 thanks a lot. I think it's not yet decided if we go with CHTML or SVG. While SVG is easier to compare to the current rendering CHTML is better.

Independent of the parent task, I think we should really soon end the (unintended) use of external services. In theory CDN providers could potentially track people.

Change #1228601 merged by jenkins-bot:

[mediawiki/extensions/Math@master] Add all MathJax (remove CDN dependency)

https://gerrit.wikimedia.org/r/1228601