Skip to content

Commit 6e9bb8f

Browse files
authored
Merge pull request #1107 from mathjax/fix/lazy-performance
Improve speech performance, especially for lazy typesetting
2 parents a614ad5 + df35028 commit 6e9bb8f

File tree

5 files changed

+69
-51
lines changed

5 files changed

+69
-51
lines changed

ts/a11y/explorer/KeyExplorer.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import type { ExplorerMathItem } from '../explorer.js';
2929
import {Explorer, AbstractExplorer} from './Explorer.js';
3030
import {ExplorerPool} from './ExplorerPool.js';
3131
import {MmlNode} from '../../core/MmlTree/MmlNode.js';
32-
import { honk, InPlace, Timing } from '../speech/SpeechUtil.js';
32+
import { honk, InPlace } from '../speech/SpeechUtil.js';
3333
import {Sre} from '../sre.js';
3434

3535

@@ -447,10 +447,9 @@ export class SpeechExplorer extends AbstractExplorer<string> implements KeyExplo
447447
* @override
448448
*/
449449
public Start() {
450-
// In case the speech is not attached yet, we restart the explorer after a
451-
// fashion.
450+
// In case the speech is not attached yet, we generate it
452451
if (this.item.state() < STATE.ATTACHSPEECH) {
453-
setTimeout(() => this.Start(), Timing.INITIAL);
452+
this.item.attachSpeech(this.document);
454453
};
455454
if (!this.attached) return;
456455
if (this.node.hasAttribute('tabindex')) {
@@ -464,7 +463,7 @@ export class SpeechExplorer extends AbstractExplorer<string> implements KeyExplo
464463
this.current = this.node.querySelector(`[data-semantic-id="${this.restarted}"]`)
465464
if (!this.current) {
466465
const dummies = Array.from(
467-
this.node.querySelectorAll(`[data-semantic-type="dummy"]`))
466+
this.node.querySelectorAll('[data-semantic-type="dummy"]'))
468467
.map(x => x.getAttribute('data-semantic-id'))
469468
let internal = this.generators.element.querySelector(
470469
`[data-semantic-id="${this.restarted}"]`);

ts/a11y/semantic-enrich.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {MathML} from '../input/mathml.js';
3030
import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
3131
import {OptionList, expandable} from '../util/Options.js';
3232
import {Sre} from './sre.js';
33-
import { buildSpeech, Timing } from './speech/SpeechUtil.js';
33+
import { buildSpeech } from './speech/SpeechUtil.js';
3434
import { GeneratorPool } from './speech/GeneratorPool.js';
3535

3636
/*==========================================================================*/
@@ -45,12 +45,12 @@ export type Constructor<T> = new(...args: any[]) => T;
4545
/**
4646
* Add STATE value for being enriched (after COMPILED and before TYPESET)
4747
*/
48-
newState('ENRICHED', 30);
48+
newState('ENRICHED', STATE.COMPILED + 10);
4949

5050
/**
51-
* Add STATE value for adding speech (after TYPESET)
51+
* Add STATE value for adding speech (after INSERTED)
5252
*/
53-
newState('ATTACHSPEECH', 205);
53+
newState('ATTACHSPEECH', STATE.INSERTED + 10);
5454

5555
/*==========================================================================*/
5656

@@ -242,7 +242,7 @@ export function EnrichedMathItemMixin<N, T, D, B extends Constructor<AbstractMat
242242
*
243243
* @return {[string, string]} Pair comprising speech and braille.
244244
*/
245-
private existingSpeech(): [string, string] {
245+
protected existingSpeech(): [string, string] {
246246
const attributes = this.root.attributes;
247247
let speech = attributes.get('aria-label') as string;
248248
if (!speech) {
@@ -378,6 +378,11 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
378378
enrich: [STATE.ENRICHED],
379379
attachSpeech: [STATE.ATTACHSPEECH]
380380
}),
381+
speechTiming: {
382+
initial: 100, // initial delay until starting to add speech
383+
threshold: 250, // time (in milliseconds) to process speech before letting screen update
384+
intermediate: 10 // delay after processing speech reaches the threshold
385+
},
381386
sre: expandable({
382387
speech: 'none', // by default no speech is included
383388
locale: 'en', // switch the locale
@@ -391,6 +396,16 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
391396
})
392397
};
393398

399+
/**
400+
* The list of MathItems that need to be processed for speech
401+
*/
402+
protected awaitingSpeech: MathItem<N, T, D>[];
403+
404+
/**
405+
* The identifier from setTimeout for the next speech loop
406+
*/
407+
protected speechTimeout: number = 0;
408+
394409
/**
395410
* Enrich the MathItem class used for this MathDocument, and create the
396411
* temporary MathItem used for enrchment
@@ -414,47 +429,41 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
414429
);
415430
}
416431

417-
private DELAY: number = Timing.INITIAL;
418-
private awaitingSpeech: MathItem<N, T, D>[] = [];
419-
420432
/**
421433
* Attach speech from a MathItem to a node
422434
*/
423435
public attachSpeech() {
424436
if (!this.processed.isSet('attach-speech')) {
425437
if (this.options.enableSpeech || this.options.enableBraille) {
438+
if (this.speechTimeout) {
439+
clearTimeout(this.speechTimeout);
440+
this.speechTimeout = 0;
441+
}
426442
this.awaitingSpeech = Array.from(this.math);
427-
this.attachSpeechStart();
443+
this.speechTimeout = setTimeout(
444+
() => this.attachSpeechLoop(),
445+
this.options.speechTiming.initial
446+
);
428447
}
429-
}
430-
return this;
431-
}
432-
433-
/**
434-
* Start attaching speech to nodes.
435-
*/
436-
private attachSpeechStart() {
437-
if (this.awaitingSpeech.length) {
438-
setTimeout(
439-
() => this.attachSpeechLoop(),
440-
this.DELAY);
441-
} else {
442448
this.processed.set('attach-speech');
443449
}
450+
return this;
444451
}
445452

446453
/**
447454
* Loops through math items to attach speech until the timeout threshold is reached.
448455
*/
449-
private attachSpeechLoop() {
456+
protected attachSpeechLoop() {
457+
const timing = this.options.speechTiming;
458+
const awaitingSpeech = this.awaitingSpeech;
450459
const timeStart = new Date().getTime();
451-
const timeEnd = timeStart + Timing.THRESHOLD;
452-
while (this.awaitingSpeech.length && new Date().getTime() < timeEnd) {
453-
const math = this.awaitingSpeech.shift();
460+
const timeEnd = timeStart + timing.threshold;
461+
do {
462+
const math = awaitingSpeech.shift();
454463
(math as EnrichedMathItem<N, T, D>).attachSpeech(this);
455-
}
456-
this.DELAY = Timing.INTERMEDIATE;
457-
this.attachSpeechStart();
464+
} while (awaitingSpeech.length && new Date().getTime() < timeEnd);
465+
this.speechTimeout = awaitingSpeech.length ?
466+
setTimeout(() => this.attachSpeechLoop(), timing.intermediate) : 0;
458467
}
459468

460469
/**

ts/a11y/speech/GeneratorPool.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ export class GeneratorPool<N, T, D> {
161161
public computeSpeech(node: N, mml: string): [string, string] {
162162
this.element = Sre.parseDOM(mml);
163163
const xml = this.prepareXml(node);
164-
const speech = this.speechGenerator.getSpeech(xml, this.element);
165-
const braille = this.brailleGenerator.getSpeech(xml, this.element);
166-
if (this.options.enableSpeech || this.options.enableBraille) {
164+
const speech = this.options.enableSpeech ? this.speechGenerator.getSpeech(xml, this.element) : '';
165+
const braille = this.options.enableBraille ? this.brailleGenerator.getSpeech(xml, this.element) : '';
166+
if (speech || braille) {
167167
this.setAria(node, xml, this.options.sre.locale);
168168
}
169169
return [speech, braille];

ts/a11y/speech/SpeechUtil.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export function buildLabel(
191191
* @param {string} speech The speech string.
192192
* @param {string=} locale An optional locale.
193193
* @param {string=} rate The base speech rate.
194-
* @return {[string, SsmlElement[]]} The speech with the ssml annotation structure
194+
* @return {[string, SsmlElement[]]} The speech with the ssml annotation structure
195195
*/
196196
export function buildSpeech(speech: string, locale: string = 'en',
197197
rate: string = '100'): [string, SsmlElement[]] {
@@ -223,12 +223,3 @@ export enum InPlace {
223223
SUMMARY
224224
}
225225

226-
/**
227-
* Some timing constants for asynchronous speech computation.
228-
*/
229-
export const Timing = {
230-
THRESHOLD: 250,
231-
INITIAL: 100,
232-
INTERMEDIATE: 10
233-
};
234-

ts/ui/lazy/LazyHandler.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323

2424
import {MathDocumentConstructor, ContainerList} from '../../core/MathDocument.js';
2525
import {MathItem, STATE, newState} from '../../core/MathItem.js';
26-
import {HTMLMathItem} from '../../handlers/html/HTMLMathItem.js';
2726
import {HTMLDocument} from '../../handlers/html/HTMLDocument.js';
2827
import {HTMLHandler} from '../../handlers/html/HTMLHandler.js';
28+
import {EnrichedMathItem} from '../../a11y/semantic-enrich.js';
2929
import {handleRetriesFor} from '../../util/Retries.js';
3030
import {OptionList} from '../../util/Options.js';
3131

@@ -151,7 +151,7 @@ export interface LazyMathItem<N, T, D> extends MathItem<N, T, D> {
151151
* @template D The Document class
152152
* @template B The MathItem class to extend
153153
*/
154-
export function LazyMathItemMixin<N, T, D, B extends Constructor<HTMLMathItem<N, T, D>>>(
154+
export function LazyMathItemMixin<N, T, D, B extends Constructor<EnrichedMathItem<N, T, D>>>(
155155
BaseMathItem: B
156156
): Constructor<LazyMathItem<N, T, D>> & B {
157157

@@ -260,6 +260,19 @@ export function LazyMathItemMixin<N, T, D, B extends Constructor<HTMLMathItem<N,
260260
}
261261
}
262262

263+
/**
264+
* Only add speech when we are actually typesetting
265+
*
266+
* @override
267+
*/
268+
public attachSpeech = (document: LazyMathDocument<N, T, D>) => {
269+
if (this.state() >= STATE.ATTACHSPEECH) return;
270+
if (!this.lazyTypeset) {
271+
super.attachSpeech?.(document);
272+
}
273+
this.state(STATE.ATTACHSPEECH);
274+
}
275+
263276
};
264277

265278
}
@@ -325,8 +338,14 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
325338
*/
326339
public static OPTIONS: OptionList = {
327340
...BaseDocument.OPTIONS,
328-
lazyMargin: '200px',
341+
lazyMargin: '500px',
329342
lazyAlwaysTypeset: null,
343+
speechTiming: {
344+
...(BaseDocument.OPTIONS.speechTiming || {}),
345+
initial: 150,
346+
threshold: 100,
347+
intermediate: 10
348+
},
330349
renderActions: {
331350
...BaseDocument.OPTIONS.renderActions,
332351
lazyAlways: [STATE.LAZYALWAYS, 'lazyAlways', '', false]
@@ -390,7 +409,7 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
390409
// Use the LazyMathItem for math items
391410
//
392411
this.options.MathItem =
393-
LazyMathItemMixin<N, T, D, Constructor<HTMLMathItem<N, T, D>>>(this.options.MathItem);
412+
LazyMathItemMixin<N, T, D, Constructor<EnrichedMathItem<N, T, D>>>(this.options.MathItem);
394413
//
395414
// Allocate a process bit for lazyAlways
396415
//

0 commit comments

Comments
 (0)