Skip to content
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
16 changes: 12 additions & 4 deletions ts/a11y/explorer/KeyExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ export class SpeechExplorer
/**
* The anchors in this expression
*/
protected anchors: HTMLElement[];
protected anchors: HTMLElement[] = [];

/**
* The elements that are focusable for tab navigation
Expand Down Expand Up @@ -1183,6 +1183,7 @@ export class SpeechExplorer
description += ', ' + localize('ForHelp');
}
speech += description;
speech = speech.replace(/^, /, '');
}
this.speak(
speech,
Expand Down Expand Up @@ -1221,7 +1222,7 @@ export class SpeechExplorer
}

/**
* Create a new speech node and sets its needed attributes,
* Create a new speech node and set its needed attributes,
* then add it to the container and focus it. If there is
* and old speech node, remove it after a delay (the delay
* is needed for Orca on Linux).
Expand All @@ -1237,6 +1238,12 @@ export class SpeechExplorer
ssml: string[] = null,
description: string = this.none
) {
if (!this.node.getAttribute('data-speech-attached') && speech) {
speech = localize('Word/Math');
}
if (!this.node.getAttribute('data-braille-attached') && braille) {
braille = localize('Word/Math');
}
const oldspeech = this.speech;
const speechNode = (this.speech = document.createElement('mjx-speech'));
speechNode.setAttribute('role', this.role);
Expand Down Expand Up @@ -1303,9 +1310,10 @@ export class SpeechExplorer
container.setAttribute('has-speech', 'true');
}
const description = item.roleDescription;
const speech =
const speech = (
(container.getAttribute(SemAttr.SPEECH) || '') +
(description && description !== this.none ? ', ' + description : '');
(description && description !== this.none ? ', ' + description : '')
).replace(/^, /, '');
this.img?.remove();
this.img = this.document.adaptor.node('mjx-speech', {
'aria-label': speech,
Expand Down
5 changes: 3 additions & 2 deletions ts/a11y/explorer/__locales__/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
"Help/Text1": "<h2 role=\"heading\" aria-level=\"2\">Ausdrücke mit %1 erkunden</h2>\n\n<p>Die mathematischen Formeln auf dieser Seite werden von <a href=\"https://www.mathjax.org/\" target=\"_blank\">MathJax</a> dargestellt, das sowohl den von Screenreadern vorgelesenen Text als auch das visuelle Layout für sehende Nutzer generiert. </p>\n\n<p>Von MathJax gesetzte Ausdrücke können interaktiv erkundet werden und sind fokussierbar. Sie können die <kbd>Tab</kbd>-Taste verwenden, um zu einem gesetzten Ausdruck zu springen%2%3. Zunächst wird der Ausdruck vollständig vorgelesen, aber Sie können die folgenden Tasten verwenden, um den Ausdruck weiter zu erkunden:</p>",
"Help/Text2": "<p>Über das MathJax-Kontextmenü können Sie die Sprach- oder Braille-Ausgabe für mathematische Ausdrücke aktivieren oder deaktivieren, die Sprache für die Sprachausgabe festlegen sowie weitere Funktionen von MathJax einstellen. Insbesondere können Sie im Untermenü „Explorer“ festlegen, wie die mathematischen Ausdrücke auf der Seite gekennzeichnet werden sollen (z. B. durch die Ansage „Mathematik“, wenn der Ausdruck vorgelesen wird) und ob eine Meldung darüber angezeigt werden soll, dass der Buchstabe „h“ dieses Dialogfeld öffnet. Wenn Sie die Sprachausgabe und die Braille-Anzeige deaktivieren, werden der Ausdrucks-Explorer, dessen Highlighting und das Hilfe-Symbol deaktiviert. </p>\n\n<p>Die Unterstützung für taktile Braille-Geräte variiert je nach Screenreader, Browser und Betriebssystem. Wenn Sie ein Braille-Ausgabegerät verwenden, müssen Sie möglicherweise die Option „Mit Sprachausgabe kombinieren“ im Untermenü „Braille“ des Kontextmenüs auswählen, um anstelle der Sprachausgabe auf Ihrem Braille-Gerät eine Nemeth- oder Euro-Braille-Ausgabe zu erhalten. %1</p>\n\n<p>Das Kontextmenü bietet außerdem Optionen zum Anzeigen oder Kopieren einer MathML-Version des Ausdrucks oder seines ursprünglichen Quellformats, zum Erstellen einer SVG-Version des Ausdrucks sowie zum Anzeigen verschiedener weiterer Informationen.</p>\n\n<p>Wenn Sie schließlich im Optionenmenü den Eintrag „MathML versteckt einbinden“ auswählen, wird die Sprach- und Braille-Generierung von MathJax deaktiviert und stattdessen visuell unsichtbares MathML verwendet, das von einigen Screenreadern vorgelesen werden kann, wobei diese Funktion nicht von allen Screenreadern und Betriebssystemen unterstützt wird. Durch Auswahl der Sprach- oder Braille-Generierung in den entsprechenden Untermenüs wird das unsichtbare MathML wieder entfernt.</p>\n\n<p>Weitere Hilfe finden Sie in der <a href=\"https://docs.mathjax.org/en/latest/basic/accessibility.html\" target=\"_blank\">MathJax-Dokumentation zur Barrierefreiheit.</a></p>",
"Help/Title": "Hilfe zum MathJax Expression Explorer",
"JumpTo": "Springe zu Zeile %1 und Spalte",
"Mac/Select": "oder die VoiceOver-Pfeiltasten, um einen Ausdruck auszuwählen",
"Mac/Title": "macOS und iOS mit VoiceOver",
"JumpTo": "Springe zu Zeile %1 und Spalte",
"PositionMarked": "Position markiert",
"Unix/Select": "Orca sollte automatisch in den Fokusmodus wechseln. Ist dies nicht der Fall, kannst du den Fokusmodus mit der Tastenkombination „Orca+a“ ein- oder ausschalten. Beachte außerdem, dass du mit „Orca+Pfeiltasten“ Ausdrücke auch im Durchsichtmodus untersuchen kannst.",
"Unix/Title": "Unix mit Orca",
"Unknown/Title": "ein Screenreader",
"Win/Braille": "NVDA-Benutzer müssen diese Option auswählen, während JAWS-Benutzer die Braille-Ausgabe erhalten sollten, ohne diese Einstellung zu ändern.",
"Win/Select": "Der Screenreader sollte automatisch in den Fokus- oder Formularmodus wechseln, sobald der Ausdruck den Browserfokus erhält. Ist dies nicht der Fall, können Sie den Fokusmodus in NVDA mit NVDA+Leertaste umschalten; bei JAWS sollte die Eingabetaste den Formularmodus aktivieren, während die Plus-Taste des Ziffernblocks ihn beendet. Beachten Sie außerdem, dass Sie die NVDA- oder JAWS-Taste zusammen mit den Pfeiltasten verwenden können, um den Ausdruck auch im Browse-Modus zu erkunden, und dass Sie mit NVDA+Umschalt+Pfeiltasten aus einem Ausdruck herausnavigieren können, der in NVDA den Fokus hat.",
"Win/Title": "Windows mit NVDA oder JAWS"
"Win/Title": "Windows mit NVDA oder JAWS",
"Word/Math": "Mathematik"
}
5 changes: 3 additions & 2 deletions ts/a11y/explorer/__locales__/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
"Help/Text1": "<h2 role=\"heading\" aria-level=\"2\">Exploring expressions with %1</h2>\n\n<p>The mathematics on this page is being rendered by <a href=\"https://www.mathjax.org/\" target=\"_blank\">MathJax</a>, which generates both the text spoken by screen readers, as well as the visual layout for sighted users.</p>\n\n<p>Expressions typeset by MathJax can be explored interactively, and are focusable. You can use the <kbd>Tab</kbd> key to move to a typeset expression%2%3. Initially, the expression will be read in full, but you can use the following keys to explore the expression further:</p>",
"Help/Text2": "<p>The MathJax contextual menu allows you to enable or disable speech or Braille generation for mathematical expressions, the language to use for the spoken mathematics, and other features of MathJax. In particular, the Explorer submenu allows you to specify how the mathematics should be identified in the page (e.g., by saying \"math\" when the expression is spoken), and whether or not to include a message about the letter \"h\" bringing up this dialog box. Turning off speech and Braille will disable the expression explorer, its highlighting, and its help icon.</p>\n\n<p>Support for tactile Braille devices varies across screen readers, browsers, and operative systems. If you are using a Braille output device, you may need to select the \"Combine with Speech\" option in the contextual menu's Braille submenu in order to obtain Nemeth or Euro Braille output rather than the speech text on your Braille device. %1</p>\n\n<p>The contextual menu also provides options for viewing or copying a MathML version of the expression or its original source format, creating an SVG version of the expression, and viewing various other information.</p>\n\n<p>Finally, selecting the \"Insert Hidden MathML\" item from the options submenu will turn of MathJax's speech and Braille generation and instead use visually hidden MathML that some screen readers can voice, though support for this is not universal across all screen readers and operating systems. Selecting speech or Braille generation in their submenus will remove the hidden MathML again.</p>\n\n<p>For more help, see the <a href=\"https://docs.mathjax.org/en/latest/basic/accessibility.html\" target=\"_blank\">MathJax accessibility documentation.</a></p>",
"Help/Title": "MathJax Expression Explorer Help",
"JumpTo": "Jump to row %1 and column",
"Mac/Select": "or the VoiceOver arrow keys to select an expression",
"Mac/Title": "MacOS and iOS using VoiceOver",
"JumpTo": "Jump to row %1 and column",
"PositionMarked": "Position marked",
"Unix/Select": "Orca should enter focus mode automatically. If not, use the Orca+a key to toggle focus mode on or off. Also note that you can use Orca+arrow keys to explore expressions even in browse mode",
"Unix/Title": "Unix using Orca",
"Unknown/Title": "a Screen Reader",
"Win/Braille": "NVDA users need to select this option, while JAWS users should be able to get Braille output without changing this setting.",
"Win/Select": "The screen reader should enter focus or forms mode automatically when the expression gets the browser focus, but if not, you can toggle focus mode using NVDA+space in NVDA; for JAWS, Enter should start forms mode while Numpad Plus leaves it. Also note that you can use the NVDA or JAWS key plus the arrow keys to explore the expression even in browse mode, and you can use NVDA+shift+arrow keys to navigate out of an expression that has the focus in NVDA",
"Win/Title": "Windows using NVDA or JAWS"
"Win/Title": "Windows using NVDA or JAWS",
"Word/Math": "math"
}
12 changes: 7 additions & 5 deletions ts/a11y/speech.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ export interface SpeechMathDocument<N, T, D> extends EnrichedMathDocument<
/**
* @param {SpeechMathDocument} doc The MathDocument for the error
* @param {SpeechMathItem} math The MathItem causing the error
* @param {Error} err The error being processed
* @param {string} err The error message being processed
*/
speechError(
doc: SpeechMathDocument<N, T, D>,
math: SpeechMathItem<N, T, D>,
err: Error
err: string
): void;

/**
Expand Down Expand Up @@ -234,7 +234,7 @@ export function SpeechMathDocumentMixin<
speechError: (
doc: SpeechMathDocument<N, T, D>,
math: SpeechMathItem<N, T, D>,
err: Error
err: string
) => doc.speechError(doc, math, err),
renderActions: expandable({
...EnrichedMathDocument.OPTIONS.renderActions,
Expand Down Expand Up @@ -316,9 +316,11 @@ export function SpeechMathDocumentMixin<
public speechError(
_doc: SpeechMathDocument<N, T, D>,
_math: SpeechMathItem<N, T, D>,
err: Error
err: string
) {
console.warn(localize('SpeechError'), err);
if (err) {
console.warn(localize('SpeechError'), err);
}
}

/**
Expand Down
9 changes: 9 additions & 0 deletions ts/a11y/speech/GeneratorPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { buildLabel, SemAttr } from '../speech/SpeechUtil.js';
import { DOMAdaptor } from '../../core/DOMAdaptor.js';
import { SpeechMathItem } from '../speech.js';
import { WorkerHandler } from './WebWorker.js';
import { localize } from '../speech/__locales__/Component.js';

/**
* @template N The HTMLElement node class
Expand Down Expand Up @@ -212,6 +213,10 @@ export class GeneratorPool<N, T, D> {
*/
public getLabel(node: N, _center: string = '', sep: string = ' '): string {
const adaptor = this.adaptor;
if (!this.webworker.ready) {
// return document.querySelector('.mjx-selected').textContent;
Comment thread
dpvc marked this conversation as resolved.
return localize('NoSpeech');
}
return (
buildLabel(
adaptor.getAttribute(node, SemAttr.SPEECH_SSML),
Expand All @@ -231,6 +236,10 @@ export class GeneratorPool<N, T, D> {
*/
public getBraille(node: N): string {
const adaptor = this.adaptor;
if (!this.webworker.ready) {
// return document.querySelector('.mjx-selected').textContent;
Comment thread
dpvc marked this conversation as resolved.
return localize('NoBraille');
}
return (
adaptor.getAttribute(node, 'aria-braillelabel') ||
adaptor.getAttribute(node, SemAttr.BRAILLE)
Expand Down
75 changes: 69 additions & 6 deletions ts/a11y/speech/WebWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { OptionList } from '../../util/Options.js';
import { Message, ClientCommand, Structure } from './MessageTypes.js';
import { SpeechMathItem } from '../speech.js';
import { SemAttr } from './SpeechUtil.js';
import { StyleJsonData } from '../../util/StyleJson.js';
import { Locale } from '../../util/Locale.js';
import { localize, COMPONENT } from './__locales__/Component.js';

Expand Down Expand Up @@ -64,6 +65,19 @@ export class WorkerHandler<N, T, D> {
*/
protected worker: minWorker;

/**
* The styles to use for the warning live region
*/
protected liveStyles: StyleJsonData = {
display: 'inline-block',
position: 'absolute',
top: 0,
left: '-10px',
width: '1px',
height: '1px',
overflow: 'hidden',
};

/**
* The adaptor to work with typeset nodes.
*
Expand Down Expand Up @@ -502,8 +516,12 @@ export class WorkerHandler<N, T, D> {
*/
public Terminate(): Promise<any> | void {
this.debug('Terminating pending tasks');
let cmd = '';
for (const task of this.tasks) {
task.reject(localize('Worker/Terminate', task.cmd.data.cmd));
task.reject(
task.cmd.cmd === cmd ? '' : localize('Worker/Terminate', task.cmd.cmd)
);
cmd = task.cmd.cmd;
}
this.tasks = [];
this.debug('Terminating worker');
Expand All @@ -515,12 +533,21 @@ export class WorkerHandler<N, T, D> {
* restarted, if desired.
*/
public async Stop() {
if (!this.worker) {
Locale.throw(COMPONENT, 'Worker/NotStarted');
if (this.worker) {
await this.Terminate();
this.worker = null;
this.ready = false;
}
await this.Terminate();
this.worker = null;
this.ready = false;
}

public LiveWarn(message: string) {
const adaptor = this.adaptor;
const live = adaptor.node('mjx-worker-live', {
'aria-live': 'assertive',
style: this.liveStyles,
});
adaptor.insert(live, adaptor.firstChild(adaptor.body()));
setTimeout(() => adaptor.append(live, adaptor.text(message)), 1000);
}

/**
Expand Down Expand Up @@ -615,5 +642,41 @@ export class WorkerHandler<N, T, D> {
console.log('Log:', data);
}
},

/**
* The webworker failed to start up properly, so report an error
*
* @param {WorkerHandler} handler The active handler for the worker.
* @param {Message} data The data received from the worker.
*/
Failed(handler: WorkerHandler<N, T, D>, data: Message) {
const { path, worker } = handler.options;
Locale.warn(
COMPONENT,
data.includes(worker) ? 'Worker/Failed' : 'Worker/FailedFrom',
data,
`${path}/${worker}`
);
handler.LiveWarn(localize('Worker/Warning'));
handler.Stop();
},

/**
* A webworker map failed to load, so report an error
*
* @param {WorkerHandler} handler The active handler for the worker.
* @param {Message} data The data received from the worker.
*/
Maps(handler: WorkerHandler<N, T, D>, data: Message) {
const { maps } = handler.options;
Locale.warn(
COMPONENT,
data.includes(maps) ? 'Worker/Maps' : 'Worker/MapsFrom',
data,
maps
);
handler.LiveWarn(localize('Worker/Warning'));
handler.Stop();
},
};
}
8 changes: 7 additions & 1 deletion ts/a11y/speech/__locales__/de.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
{
"NoBraille": "Braille nicht verfügbar",
"NoSpeech": "Sprache nicht verfügbar",
"ProsodyError": "Beim Prosody-Abgleich ist ein Fehler aufgetreten",
"SpeechError": "Fehler bei der Sprachgenerierung:",
"Worker/Cancelled": "Task %1 abgebrochen",
"Worker/Failed": "MathJax Webworker konnte nicht geladen werden: %1",
"Worker/FailedFrom": "MathJax Webworker konnte nicht von %2 geladen werden: %1",
"Worker/Warning": "Warnung: MathJax kann für mathematische Formeln auf dieser Seite keine Sprachausgabe erzeugen.",
"Worker/Maps": "MathJax Webworker konnte die mathematische Zuordnung nicht laden: %1",
"Worker/MapsFrom": "MathJax Webworker konnte die mathematische Zuordnung nicht von %2 laden: %1",
"Worker/NotStarted": "Worker wurde nicht gestartet",
"Worker/Started": "Worker bereits gestartet",
"Worker/Terminate": "%1 durch Beendigung des WorkerHandlers abgebrochen"
}
8 changes: 7 additions & 1 deletion ts/a11y/speech/__locales__/en.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
{
"NoBraille": "Braille not available",
"NoSpeech": "Speech not available",
"ProsodyError": "Something went wrong with the prosody matching",
"SpeechError": "Speech generation error:",
"Worker/Cancelled": "Task %1 cancelled",
"Worker/Failed": "MathJax webworker failed to load: %1",
"Worker/FailedFrom": "MathJax webworker failed to load from %2: %1",
"Worker/Warning": "Warning: MathJax is not able to generate speech for the mathematics on this page",
"Worker/Maps": "MathJax webworker failed to load math map: %1",
"Worker/MapsFrom": "MathJax webworker failed to load math map from %2: %1",
"Worker/NotStarted": "Worker has not been started",
"Worker/Started": "Worker already started",
"Worker/Terminate": "%1 cancelled by WorkerHandler termination"
}
15 changes: 12 additions & 3 deletions ts/a11y/sre/speech-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,14 @@ declare const SRE: any;
if (!global.require) {
await import(/* webpackIgnore: true */ './require.mjs');
}
global.getMap = (file: string) =>
Promise.resolve(JSON.stringify(global.require(file)));
global.getMap = (file: string) => {
try {
return Promise.resolve(JSON.stringify(global.require(file)));
} catch (err) {
Client('Maps', err.message.split(/\n/)[0]);
return {};
}
};
} else {
//
// For web workers, make global be the self object
Expand All @@ -97,7 +103,10 @@ declare const SRE: any;
global.getMap = (file: string) =>
fetch(file)
.then((data) => data.json())
.catch((err) => console.log(err));
.catch((err) => {
Client('Maps', err.message);
return {};
});
}

//
Expand Down
6 changes: 5 additions & 1 deletion ts/adaptors/HTMLAdaptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,11 @@ export class HTMLAdaptor<
const file = `${path}/${worker}`;
const content = `
self.maps = '${quoted(maps)}';
importScripts('${quoted(file)}');
try {
importScripts('${quoted(file)}');
} catch (e) {
postMessage({cmd: 'Failed', data: e.message});
}
`;
const url = URL.createObjectURL(
new Blob([content], { type: 'text/javascript' })
Expand Down
10 changes: 8 additions & 2 deletions ts/adaptors/NodeMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,19 @@ export function NodeMixin<N, T, D, A extends AdaptorConstructor<N, T, D>>(
this.worker.terminate();
}
}
const { path, maps } = options;
const url = `${path}/${options.worker}`;
const { path, maps, worker: file } = options;
const url = `${path}/${file}`;
const worker = new LiteWorker(url, {
type: 'module',
workerData: { maps },
});
worker.addEventListener('message', listener);
worker.addEventListener('error', (err) => {
const event = new MessageEvent('error', {
data: { cmd: 'Failed', data: err.message },
});
listener(event);
});
return worker;
}
};
Expand Down
3 changes: 3 additions & 0 deletions ts/util/AsyncLoad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export function resolvePath(
relative: (name: string) => string,
absolute: (name: string) => string = (name) => name
): string {
if (name.substring(0, 5) === 'node:') {
return name.substring(5);
}
const Package =
typeof MathJax === 'undefined'
? null
Expand Down