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

Improve performance by detaching speech computation #1103

Merged
merged 4 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion ts/a11y/explorer/KeyExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@


import {A11yDocument, HoverRegion, SpeechRegion, LiveRegion} from './Region.js';
import {STATE} from '../../core/MathItem.js';
import type { ExplorerMathItem } from '../explorer.js';
import {Explorer, AbstractExplorer} from './Explorer.js';
import {ExplorerPool} from './ExplorerPool.js';
Expand Down Expand Up @@ -446,6 +447,10 @@ export class SpeechExplorer extends AbstractExplorer<string> implements KeyExplo
* @override
*/
public Start() {
// In case the speech is not attached yet, we generate it
if (this.item.state() < STATE.ATTACHSPEECH) {
this.item.attachSpeech(this.document);
};
if (!this.attached) return;
if (this.node.hasAttribute('tabindex')) {
this.node.removeAttribute('tabindex');
Expand All @@ -458,7 +463,7 @@ export class SpeechExplorer extends AbstractExplorer<string> implements KeyExplo
this.current = this.node.querySelector(`[data-semantic-id="${this.restarted}"]`)
if (!this.current) {
const dummies = Array.from(
this.node.querySelectorAll(`[data-semantic-type="dummy"]`))
this.node.querySelectorAll('[data-semantic-type="dummy"]'))
.map(x => x.getAttribute('data-semantic-id'))
let internal = this.generators.element.querySelector(
`[data-semantic-id="${this.restarted}"]`);
Expand Down
50 changes: 43 additions & 7 deletions ts/a11y/semantic-enrich.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
import {OptionList, expandable} from '../util/Options.js';
import {Sre} from './sre.js';
import { buildSpeech } from './speech/SpeechUtil.js';

import { GeneratorPool } from './speech/GeneratorPool.js';

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

/**
* Add STATE value for adding speech (after TYPESET)
* Add STATE value for adding speech (after INSERTED)
*/
newState('ATTACHSPEECH', 155);
newState('ATTACHSPEECH', STATE.INSERTED + 10);

/*==========================================================================*/

Expand Down Expand Up @@ -243,7 +242,7 @@ export function EnrichedMathItemMixin<N, T, D, B extends Constructor<AbstractMat
*
* @return {[string, string]} Pair comprising speech and braille.
*/
private existingSpeech(): [string, string] {
protected existingSpeech(): [string, string] {
const attributes = this.root.attributes;
let speech = attributes.get('aria-label') as string;
if (!speech) {
Expand Down Expand Up @@ -379,6 +378,11 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
enrich: [STATE.ENRICHED],
attachSpeech: [STATE.ATTACHSPEECH]
}),
speechTiming: {
initial: 100, // initial delay until starting to add speech
threshold: 250, // time (in milliseconds) to process speech before letting screen update
intermediate: 10 // delay after processing speech reaches the threshold
},
sre: expandable({
speech: 'none', // by default no speech is included
locale: 'en', // switch the locale
Expand All @@ -392,6 +396,16 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
})
};

/**
* The list of MathItems that need to be processed for speech
*/
protected awaitingSpeech: MathItem<N, T, D>[];

/**
* The identifier from setTimeout for the next speech loop
*/
protected speechTimeout: number = 0;

/**
* Enrich the MathItem class used for this MathDocument, and create the
* temporary MathItem used for enrchment
Expand Down Expand Up @@ -421,15 +435,37 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
public attachSpeech() {
if (!this.processed.isSet('attach-speech')) {
if (this.options.enableSpeech || this.options.enableBraille) {
for (const math of this.math) {
(math as EnrichedMathItem<N, T, D>).attachSpeech(this);
if (this.speechTimeout) {
clearTimeout(this.speechTimeout);
this.speechTimeout = 0;
}
this.awaitingSpeech = Array.from(this.math);
this.speechTimeout = setTimeout(
() => this.attachSpeechLoop(),
this.options.speechTiming.initial
);
}
this.processed.set('attach-speech');
}
return this;
}

/**
* Loops through math items to attach speech until the timeout threshold is reached.
*/
protected attachSpeechLoop() {
const timing = this.options.speechTiming;
const awaitingSpeech = this.awaitingSpeech;
const timeStart = new Date().getTime();
const timeEnd = timeStart + timing.threshold;
do {
const math = awaitingSpeech.shift();
(math as EnrichedMathItem<N, T, D>).attachSpeech(this);
} while (awaitingSpeech.length && new Date().getTime() < timeEnd);
this.speechTimeout = awaitingSpeech.length ?
setTimeout(() => this.attachSpeechLoop(), timing.intermediate) : 0;
}

/**
* Enrich the MathItems in this MathDocument
*/
Expand Down
6 changes: 3 additions & 3 deletions ts/a11y/speech/GeneratorPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ export class GeneratorPool<N, T, D> {
public computeSpeech(node: N, mml: string): [string, string] {
this.element = Sre.parseDOM(mml);
const xml = this.prepareXml(node);
const speech = this.speechGenerator.getSpeech(xml, this.element);
const braille = this.brailleGenerator.getSpeech(xml, this.element);
if (this.options.enableSpeech || this.options.enableBraille) {
const speech = this.options.enableSpeech ? this.speechGenerator.getSpeech(xml, this.element) : '';
const braille = this.options.enableBraille ? this.brailleGenerator.getSpeech(xml, this.element) : '';
if (speech || braille) {
this.setAria(node, xml, this.options.sre.locale);
}
return [speech, braille];
Expand Down
2 changes: 1 addition & 1 deletion ts/a11y/speech/SpeechUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export function buildLabel(
* @param {string} speech The speech string.
* @param {string=} locale An optional locale.
* @param {string=} rate The base speech rate.
* @return {[string, SsmlElement[]]} The speech with the ssml annotation structure
* @return {[string, SsmlElement[]]} The speech with the ssml annotation structure
*/
export function buildSpeech(speech: string, locale: string = 'en',
rate: string = '100'): [string, SsmlElement[]] {
Expand Down
27 changes: 23 additions & 4 deletions ts/ui/lazy/LazyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@

import {MathDocumentConstructor, ContainerList} from '../../core/MathDocument.js';
import {MathItem, STATE, newState} from '../../core/MathItem.js';
import {HTMLMathItem} from '../../handlers/html/HTMLMathItem.js';
import {HTMLDocument} from '../../handlers/html/HTMLDocument.js';
import {HTMLHandler} from '../../handlers/html/HTMLHandler.js';
import {EnrichedMathItem} from '../../a11y/semantic-enrich.js';
import {handleRetriesFor} from '../../util/Retries.js';
import {OptionList} from '../../util/Options.js';

Expand Down Expand Up @@ -151,7 +151,7 @@ export interface LazyMathItem<N, T, D> extends MathItem<N, T, D> {
* @template D The Document class
* @template B The MathItem class to extend
*/
export function LazyMathItemMixin<N, T, D, B extends Constructor<HTMLMathItem<N, T, D>>>(
export function LazyMathItemMixin<N, T, D, B extends Constructor<EnrichedMathItem<N, T, D>>>(
BaseMathItem: B
): Constructor<LazyMathItem<N, T, D>> & B {

Expand Down Expand Up @@ -260,6 +260,19 @@ export function LazyMathItemMixin<N, T, D, B extends Constructor<HTMLMathItem<N,
}
}

/**
* Only add speech when we are actually typesetting
*
* @override
*/
public attachSpeech = (document: LazyMathDocument<N, T, D>) => {
if (this.state() >= STATE.ATTACHSPEECH) return;
if (!this.lazyTypeset) {
super.attachSpeech?.(document);
}
this.state(STATE.ATTACHSPEECH);
}

};

}
Expand Down Expand Up @@ -325,8 +338,14 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
*/
public static OPTIONS: OptionList = {
...BaseDocument.OPTIONS,
lazyMargin: '200px',
lazyMargin: '500px',
lazyAlwaysTypeset: null,
speechTiming: {
...(BaseDocument.OPTIONS.speechTiming || {}),
initial: 150,
threshold: 100,
intermediate: 10
},
renderActions: {
...BaseDocument.OPTIONS.renderActions,
lazyAlways: [STATE.LAZYALWAYS, 'lazyAlways', '', false]
Expand Down Expand Up @@ -390,7 +409,7 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
// Use the LazyMathItem for math items
//
this.options.MathItem =
LazyMathItemMixin<N, T, D, Constructor<HTMLMathItem<N, T, D>>>(this.options.MathItem);
LazyMathItemMixin<N, T, D, Constructor<EnrichedMathItem<N, T, D>>>(this.options.MathItem);
//
// Allocate a process bit for lazyAlways
//
Expand Down