explorer.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. /*************************************************************
  2. *
  3. * [Contrib]/a11y/explorer.js
  4. *
  5. * Implements expression exploration via the SRE explorer.
  6. *
  7. * ---------------------------------------------------------------------
  8. *
  9. * Copyright (c) 2016-2017 The MathJax Consortium
  10. *
  11. * Licensed under the Apache License, Version 2.0 (the "License");
  12. * you may not use this file except in compliance with the License.
  13. * You may obtain a copy of the License at
  14. *
  15. * http://www.apache.org/licenses/LICENSE-2.0
  16. *
  17. * Unless required by applicable law or agreed to in writing, software
  18. * distributed under the License is distributed on an "AS IS" BASIS,
  19. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20. * See the License for the specific language governing permissions and
  21. * limitations under the License.
  22. */
  23. MathJax.Hub.Register.StartupHook('Sre Ready', function() {
  24. var FALSE, KEY;
  25. var SETTINGS = MathJax.Hub.config.menuSettings;
  26. var COOKIE = {}; // replaced when menu is available
  27. MathJax.Hub.Register.StartupHook('MathEvents Ready', function() {
  28. FALSE = MathJax.Extension.MathEvents.Event.False;
  29. KEY = MathJax.Extension.MathEvents.Event.KEY;
  30. });
  31. var Assistive = MathJax.Extension.explorer = {
  32. version: '1.2.3',
  33. dependents: [], // the extensions that depend on this one
  34. //
  35. // Default configurations.
  36. //
  37. defaults: {
  38. walker: 'syntactic',
  39. highlight: 'none',
  40. background: 'blue',
  41. foreground: 'black',
  42. speech: true,
  43. generation: 'lazy',
  44. subtitle: false,
  45. ruleset: 'mathspeak-default'
  46. },
  47. eagerComplexity: 80,
  48. prefix: 'Assistive-',
  49. hook: null,
  50. oldrules: null,
  51. addMenuOption: function(key, value) {
  52. SETTINGS[Assistive.prefix + key] = value;
  53. },
  54. addDefaults: function() {
  55. var defaults = MathJax.Hub.CombineConfig('explorer', Assistive.defaults);
  56. var keys = Object.keys(defaults);
  57. for (var i = 0, key; key = keys[i]; i++) {
  58. if (typeof(SETTINGS[Assistive.prefix + key]) === 'undefined') {
  59. Assistive.addMenuOption(key, defaults[key]);
  60. }
  61. }
  62. Assistive.setSpeechOption();
  63. Explorer.Reset();
  64. },
  65. setOption: function(key, value) {
  66. if (SETTINGS[Assistive.prefix + key] === value) return;
  67. Assistive.addMenuOption(key, value);
  68. Explorer.Reset();
  69. },
  70. getOption: function(key) {
  71. return SETTINGS[Assistive.prefix + key];
  72. },
  73. speechOption: function(msg) {
  74. if (Assistive.oldrules === msg.value) return;
  75. Assistive.setSpeechOption();
  76. Explorer.Regenerate();
  77. },
  78. setSpeechOption: function() {
  79. var ruleset = SETTINGS[Assistive.prefix + 'ruleset'];
  80. var cstr = ruleset.split('-');
  81. sre.System.getInstance().setupEngine({
  82. domain: Assistive.Domain(cstr[0]),
  83. style: cstr[1],
  84. rules: Assistive.RuleSet(cstr[0])
  85. });
  86. Assistive.oldrules = ruleset;
  87. },
  88. Domain: function(domain) {
  89. switch (domain) {
  90. case 'chromevox':
  91. return 'default';
  92. case 'mathspeak':
  93. default:
  94. return 'mathspeak';
  95. }
  96. },
  97. RuleSet: function(domain) {
  98. switch (domain) {
  99. case 'chromevox':
  100. return ['AbstractionRules', 'SemanticTreeRules'];
  101. case 'mathspeak':
  102. default:
  103. return ['AbstractionRules', 'MathspeakRules'];
  104. }
  105. },
  106. hook: null,
  107. Enable: function(update, menu) {
  108. SETTINGS.explorer = true;
  109. if (menu) COOKIE.explorer = true;
  110. MathJax.Extension.collapsible.Enable(false, menu);
  111. if (MathJax.Extension.AssistiveMML) {
  112. MathJax.Extension.AssistiveMML.config.disabled = true;
  113. SETTINGS.assistiveMML = false;
  114. if (menu) COOKIE.assistiveMML = false;
  115. }
  116. this.DisableMenus(false);
  117. if (!this.hook) {
  118. this.hook = MathJax.Hub.Register.MessageHook(
  119. 'New Math', ['Register', this.Explorer]);
  120. }
  121. if (update) MathJax.Hub.Queue(['Reprocess', MathJax.Hub]);
  122. },
  123. Disable: function(update, menu) {
  124. SETTINGS.explorer = false;
  125. if (menu) COOKIE.explorer = false;
  126. this.DisableMenus(true);
  127. if (this.hook) {
  128. MathJax.Hub.UnRegister.MessageHook(this.hook);
  129. this.hook = null;
  130. }
  131. for (var i = this.dependents.length - 1; i >= 0; i--) {
  132. var dependent = this.dependents[i];
  133. if (dependent.Disable) dependent.Disable(false, menu);
  134. }
  135. // Reprocess on update? I don't think it is necessary
  136. // (now that we check for being enabled in the event handlers)
  137. },
  138. DisableMenus: function(state) {
  139. if (MathJax.Menu) {
  140. var menu = MathJax.Menu.menu.FindId('Accessibility', 'Explorer');
  141. if (menu) {
  142. menu = menu.submenu;
  143. var items = menu.items;
  144. for (var i = 2, item; item = items[i]; i++) item.disabled = state;
  145. if (!state && menu.FindId('SpeechOutput') && !SETTINGS[Assistive.prefix + 'speech']) {
  146. menu.FindId('Subtitles').disabled = true;
  147. menu.FindId('Generation').disabled = true;
  148. }
  149. }
  150. }
  151. },
  152. //
  153. // Register a dependent
  154. //
  155. Dependent: function(extension) {
  156. this.dependents.push(extension);
  157. }
  158. };
  159. var LiveRegion = MathJax.Object.Subclass({
  160. div: null,
  161. inner: null,
  162. Init: function() {
  163. this.div = LiveRegion.Create('assertive');
  164. this.inner = MathJax.HTML.addElement(this.div, 'div');
  165. },
  166. //
  167. // Adds the speech div.
  168. //
  169. Add: function() {
  170. if (LiveRegion.added) return;
  171. document.body.appendChild(this.div);
  172. LiveRegion.added = true;
  173. },
  174. //
  175. // Shows the live region as a subtitle of a node.
  176. //
  177. Show: function(node, highlighter) {
  178. this.div.classList.add('MJX_LiveRegion_Show');
  179. var rect = node.getBoundingClientRect();
  180. var bot = rect.bottom + 10 + window.pageYOffset;
  181. var left = rect.left + window.pageXOffset;
  182. this.div.style.top = bot + 'px';
  183. this.div.style.left = left + 'px';
  184. var color = highlighter.colorString();
  185. this.inner.style.backgroundColor = color.background;
  186. this.inner.style.color = color.foreground;
  187. },
  188. //
  189. // Takes the live region out of the page flow.
  190. //
  191. Hide: function(node) {
  192. this.div.classList.remove('MJX_LiveRegion_Show');
  193. },
  194. //
  195. // Clears the speech div.
  196. //
  197. Clear: function() {
  198. this.Update('');
  199. this.inner.style.top = '';
  200. this.inner.style.backgroundColor = '';
  201. },
  202. //
  203. // Speaks a string by poking it into the speech div.
  204. //
  205. Update: function(speech) {
  206. if (Assistive.getOption('speech')) {
  207. LiveRegion.Update(this.inner, speech);
  208. }
  209. }
  210. }, {
  211. ANNOUNCE: 'Navigatable Math in page. Explore with shift space and arrow' +
  212. ' keys. Expand or collapse elements hitting enter.',
  213. announced: false,
  214. added: false,
  215. styles: {'.MJX_LiveRegion':
  216. {
  217. position: 'absolute', top: '0', height: '1px', width: '1px',
  218. padding: '1px', overflow: 'hidden'
  219. },
  220. '.MJX_LiveRegion_Show':
  221. {
  222. top: '0', position: 'absolute', width: 'auto', height: 'auto',
  223. padding: '0px 0px', opacity: 1, 'z-index': '202',
  224. left: 0, right: 0, 'margin': '0 auto',
  225. 'background-color': 'white', 'box-shadow': '0px 10px 20px #888',
  226. border: '2px solid #CCCCCC'
  227. }
  228. },
  229. //
  230. // Creates a live region with a particular type.
  231. //
  232. Create: function(type) {
  233. var element = MathJax.HTML.Element(
  234. 'div', {className: 'MJX_LiveRegion'});
  235. element.setAttribute('aria-live', type);
  236. return element;
  237. },
  238. //
  239. // Updates a live region's text content.
  240. //
  241. Update: MathJax.Hub.Browser.isPC ?
  242. function(div, speech) {
  243. div.textContent = '';
  244. setTimeout(function() {div.textContent = speech;}, 100);
  245. } : function(div, speech) {
  246. div.textContent = '';
  247. div.textContent = speech;
  248. },
  249. //
  250. // Speaks the announce string.
  251. //
  252. Announce: function() {
  253. if (!Assistive.getOption('speech')) return;
  254. LiveRegion.announced = true;
  255. MathJax.Ajax.Styles(LiveRegion.styles);
  256. var div = LiveRegion.Create('polite');
  257. document.body.appendChild(div);
  258. LiveRegion.Update(div, LiveRegion.ANNOUNCE);
  259. setTimeout(function() {document.body.removeChild(div);}, 1000);
  260. }
  261. });
  262. MathJax.Extension.explorer.LiveRegion = LiveRegion;
  263. var A11Y_PATH = MathJax.Ajax.fileURL(MathJax.Ajax.config.path.a11y);
  264. var Explorer = MathJax.Extension.explorer.Explorer = {
  265. liveRegion: LiveRegion(),
  266. walker: null,
  267. highlighter: null,
  268. hoverer: null,
  269. flamer: null,
  270. speechDiv: null,
  271. earconFile: A11Y_PATH + '/invalid_keypress' +
  272. (['Firefox', 'Chrome', 'Opera'].indexOf(MathJax.Hub.Browser.name) !== -1 ?
  273. '.ogg' : '.mp3'),
  274. expanded: false,
  275. focusoutEvent: MathJax.Hub.Browser.isFirefox ? 'blur' : 'focusout',
  276. focusinEvent: 'focus',
  277. ignoreFocusOut: false,
  278. jaxCache: {},
  279. messageID: null,
  280. //
  281. // Resets the explorer, rerunning methods not triggered by events.
  282. //
  283. Reset: function() {
  284. Explorer.FlameEnriched();
  285. },
  286. //
  287. // Registers new Maths and adds a key event if it is enriched.
  288. //
  289. Register: function(msg) {
  290. if (!Assistive.hook) return;
  291. var script = document.getElementById(msg[1]);
  292. if (script && script.id) {
  293. var jax = MathJax.Hub.getJaxFor(script.id);
  294. if (jax && jax.enriched) {
  295. Explorer.StateChange(script.id, jax);
  296. Explorer.liveRegion.Add();
  297. Explorer.AddEvent(script);
  298. }
  299. }
  300. },
  301. StateChange: function(id, jax) {
  302. Explorer.GetHighlighter(.2);
  303. var oldJax = Explorer.jaxCache[id];
  304. if (oldJax && oldJax === jax.root) return;
  305. if (oldJax) {
  306. Explorer.highlighter.resetState(id + '-Frame');
  307. }
  308. Explorer.jaxCache[id] = jax.root;
  309. },
  310. //
  311. // Adds Aria attributes.
  312. //
  313. AddAria: function(math) {
  314. math.setAttribute('role', 'application');
  315. math.setAttribute('aria-label', 'Math');
  316. },
  317. //
  318. // Add hook to run at End Math to restart walking on an expansion element.
  319. //
  320. AddHook: function(jax) {
  321. Explorer.RemoveHook();
  322. Explorer.hook = MathJax.Hub.Register.MessageHook(
  323. 'End Math', function(message) {
  324. var newid = message[1].id + '-Frame';
  325. var math = document.getElementById(newid);
  326. if (jax && newid === Explorer.expanded) {
  327. Explorer.ActivateWalker(math, jax);
  328. math.focus();
  329. Explorer.expanded = false;
  330. }
  331. });
  332. },
  333. //
  334. // Remove and unregister the explorer hook.
  335. //
  336. RemoveHook: function() {
  337. if (Explorer.hook) {
  338. MathJax.Hub.UnRegister.MessageHook(Explorer.hook);
  339. Explorer.hook = null;
  340. }
  341. },
  342. AddMessage: function() {
  343. return MathJax.Message.Set('Generating Speech Output');
  344. },
  345. RemoveMessage: function(id) {
  346. if (id) MathJax.Message.Clear(id);
  347. },
  348. //
  349. // Adds a key event to an enriched jax.
  350. //
  351. AddEvent: function(script) {
  352. var id = script.id + '-Frame';
  353. var sibling = script.previousSibling;
  354. if (!sibling) return;
  355. var math = sibling.id !== id ? sibling.firstElementChild : sibling;
  356. Explorer.AddAria(math);
  357. Explorer.AddMouseEvents(math);
  358. if (math.className === 'MathJax_MathML') {
  359. math = math.firstElementChild;
  360. }
  361. if (!math) return;
  362. math.onkeydown = Explorer.Keydown;
  363. Explorer.Flame(math);
  364. math.addEventListener(
  365. Explorer.focusinEvent,
  366. function(event) {
  367. if (!Assistive.hook) return;
  368. if (!LiveRegion.announced) LiveRegion.Announce();
  369. });
  370. math.addEventListener(
  371. Explorer.focusoutEvent,
  372. function(event) {
  373. if (!Assistive.hook) return;
  374. // A fix for Edge.
  375. if (Explorer.ignoreFocusOut) {
  376. Explorer.ignoreFocusOut = false;
  377. if (Explorer.walker.moved === 'enter') {
  378. event.target.focus();
  379. return;
  380. }
  381. }
  382. if (Explorer.walker) Explorer.DeactivateWalker();
  383. });
  384. //
  385. if (Assistive.getOption('speech')) {
  386. Explorer.AddSpeech(math);
  387. }
  388. //
  389. },
  390. //
  391. // Add speech output.
  392. //
  393. AddSpeech: function(math) {
  394. var id = math.id;
  395. var jax = MathJax.Hub.getJaxFor(id);
  396. var mathml = jax.root.toMathML();
  397. if (!math.getAttribute('haslabel')) {
  398. Explorer.AddMathLabel(mathml, id);
  399. }
  400. if (math.getAttribute('hasspeech')) return;
  401. switch (Assistive.getOption('generation')) {
  402. case 'eager':
  403. Explorer.AddSpeechEager(mathml, id);
  404. break;
  405. case 'mixed':
  406. var complexity = math.querySelectorAll('[data-semantic-complexity]');
  407. if (complexity.length >= Assistive.eagerComplexity) {
  408. Explorer.AddSpeechEager(mathml, id);
  409. }
  410. break;
  411. case 'lazy':
  412. default:
  413. break;
  414. }
  415. },
  416. AddSpeechLazy: function(math) {
  417. var generator = new sre.TreeSpeechGenerator();
  418. generator.setRebuilt(Explorer.walker.rebuilt);
  419. generator.getSpeech(Explorer.walker.rootNode, Explorer.walker.xml);
  420. math.setAttribute('hasspeech', 'true');
  421. },
  422. //
  423. //
  424. // Adds speech strings to the node using a web worker.
  425. //
  426. AddSpeechEager: function(mathml, id) {
  427. Explorer.MakeSpeechTask(
  428. mathml, id, sre.TreeSpeechGenerator,
  429. function(math, speech) {math.setAttribute('hasspeech', 'true');}, 5);
  430. },
  431. //
  432. // Attaches the Math expression as an aria label.
  433. //
  434. AddMathLabel: function(mathml, id) {
  435. Explorer.MakeSpeechTask(
  436. mathml, id, sre.SummarySpeechGenerator,
  437. function(math, speech) {
  438. math.setAttribute('haslabel', 'true');
  439. math.setAttribute('aria-label', speech);},
  440. 5);
  441. },
  442. //
  443. // The actual speech task generator.
  444. //
  445. MakeSpeechTask: function(mathml, id, constructor, onSpeech, time) {
  446. var messageID = Explorer.AddMessage();
  447. setTimeout(function() {
  448. var speechGenerator = new constructor();
  449. var math = document.getElementById(id);
  450. var dummy = new sre.DummyWalker(
  451. math, speechGenerator, Explorer.highlighter, mathml);
  452. var speech = dummy.speech();
  453. if (speech) {
  454. onSpeech(math, speech);
  455. }
  456. Explorer.RemoveMessage(messageID);
  457. }, time);
  458. },
  459. //
  460. // Event execution on keydown. Subsumes the same method of MathEvents.
  461. //
  462. Keydown: function(event) {
  463. if (event.keyCode === KEY.ESCAPE) {
  464. if (!Explorer.walker) return;
  465. Explorer.RemoveHook();
  466. Explorer.DeactivateWalker();
  467. FALSE(event);
  468. return;
  469. }
  470. // If walker is active we redirect there.
  471. if (Explorer.walker && Explorer.walker.isActive()) {
  472. var move = Explorer.walker.move(event.keyCode);
  473. if (move === null) return;
  474. if (move) {
  475. if (Explorer.walker.moved === 'expand') {
  476. Explorer.expanded = Explorer.walker.node.id;
  477. // This sometimes blurs in Edge and sometimes it does not.
  478. if (MathJax.Hub.Browser.isEdge) {
  479. Explorer.ignoreFocusOut = true;
  480. Explorer.DeactivateWalker();
  481. return;
  482. }
  483. // This does not blur in FF, IE.
  484. if (MathJax.Hub.Browser.isFirefox || MathJax.Hub.Browser.isMSIE) {
  485. Explorer.DeactivateWalker();
  486. return;
  487. }
  488. }
  489. Explorer.liveRegion.Update(Explorer.walker.speech());
  490. Explorer.Highlight();
  491. } else {
  492. Explorer.PlayEarcon();
  493. }
  494. FALSE(event);
  495. return;
  496. }
  497. var math = event.target;
  498. if (event.keyCode === KEY.SPACE) {
  499. if (event.shiftKey && Assistive.hook) {
  500. var jax = MathJax.Hub.getJaxFor(math);
  501. Explorer.ActivateWalker(math, jax);
  502. Explorer.AddHook(jax);
  503. } else {
  504. MathJax.Extension.MathEvents.Event.ContextMenu(event, math);
  505. }
  506. FALSE(event);
  507. return;
  508. }
  509. },
  510. GetHighlighter: function(alpha) {
  511. Explorer.highlighter = sre.HighlighterFactory.highlighter(
  512. {color: Assistive.getOption('background'), alpha: alpha},
  513. {color: Assistive.getOption('foreground'), alpha: 1},
  514. {renderer: MathJax.Hub.outputJax['jax/mml'][0].id,
  515. browser: MathJax.Hub.Browser.name}
  516. );
  517. },
  518. //
  519. // Adds mouse events to maction items in an enriched jax.
  520. //
  521. AddMouseEvents: function(node) {
  522. sre.HighlighterFactory.addEvents(
  523. node,
  524. {'mouseover': Explorer.MouseOver,
  525. 'mouseout': Explorer.MouseOut},
  526. {renderer: MathJax.Hub.outputJax['jax/mml'][0].id,
  527. browser: MathJax.Hub.Browser.name}
  528. );
  529. },
  530. MouseOver: function(event) {
  531. if (Assistive.getOption('highlight') === 'none') return;
  532. if (Assistive.getOption('highlight') === 'hover') {
  533. var frame = event.currentTarget;
  534. Explorer.GetHighlighter(.1);
  535. Explorer.highlighter.highlight([frame]);
  536. Explorer.hoverer = true;
  537. }
  538. FALSE(event);
  539. },
  540. MouseOut: function(event) {
  541. if (Explorer.hoverer) {
  542. Explorer.highlighter.unhighlight();
  543. Explorer.hoverer = false;
  544. }
  545. return FALSE(event);
  546. },
  547. //
  548. // Activates Flaming
  549. //
  550. Flame: function(node) {
  551. if (Assistive.getOption('highlight') === 'flame') {
  552. Explorer.GetHighlighter(.05);
  553. Explorer.highlighter.highlightAll(node);
  554. Explorer.flamer = true;
  555. return;
  556. }
  557. },
  558. UnFlame: function() {
  559. if (Explorer.flamer) {
  560. Explorer.highlighter.unhighlightAll();
  561. Explorer.flamer = null;
  562. }
  563. },
  564. FlameEnriched: function() {
  565. Explorer.UnFlame();
  566. for (var i = 0, all = MathJax.Hub.getAllJax(), jax; jax = all[i]; i++) {
  567. Explorer.Flame(jax.SourceElement().previousSibling);
  568. }
  569. },
  570. //
  571. // Activates the walker.
  572. //
  573. Walkers: {
  574. 'syntactic': sre.SyntaxWalker,
  575. 'semantic': sre.SemanticWalker,
  576. 'none': sre.DummyWalker
  577. },
  578. ActivateWalker: function(math, jax) {
  579. var speechOn = Assistive.getOption('speech');
  580. var constructor = Explorer.Walkers[Assistive.getOption('walker')] ||
  581. Explorer.Walkers['none'];
  582. var speechGenerator = speechOn ? new sre.DirectSpeechGenerator() :
  583. new sre.DummySpeechGenerator();
  584. Explorer.GetHighlighter(.2);
  585. Explorer.walker = new constructor(
  586. math, speechGenerator, Explorer.highlighter, jax.root.toMathML());
  587. if (speechOn && !math.getAttribute('hasspeech')) {
  588. Explorer.AddSpeechLazy(math);
  589. }
  590. Explorer.walker.activate();
  591. if (speechOn) {
  592. if (Assistive.getOption('subtitle')) {
  593. Explorer.liveRegion.Show(math, Explorer.highlighter);
  594. }
  595. Explorer.liveRegion.Update(Explorer.walker.speech());
  596. }
  597. Explorer.Highlight();
  598. // A fix for Edge.
  599. if (Explorer.ignoreFocusOut) {
  600. setTimeout(function() {Explorer.ignoreFocusOut = false;}, 500);
  601. }
  602. },
  603. //
  604. // Deactivates the walker.
  605. //
  606. DeactivateWalker: function() {
  607. Explorer.liveRegion.Clear();
  608. Explorer.liveRegion.Hide();
  609. Explorer.Unhighlight();
  610. Explorer.currentHighlight = null;
  611. Explorer.walker.deactivate();
  612. Explorer.walker = null;
  613. },
  614. //
  615. // Highlights the focused nodes.
  616. //
  617. Highlight: function() {
  618. Explorer.Unhighlight();
  619. Explorer.highlighter.highlight(Explorer.walker.getFocus().getNodes());
  620. },
  621. //
  622. // Unhighlights the old nodes.
  623. //
  624. Unhighlight: function() {
  625. Explorer.highlighter.unhighlight();
  626. },
  627. //
  628. // Plays the earcon.
  629. //
  630. // Every time we make new Audio element, as some browsers do not allow to
  631. // play audio elements more than once (e.g., Safari).
  632. //
  633. PlayEarcon: function() {
  634. var audio = new Audio(Explorer.earconFile);
  635. audio.play();
  636. },
  637. //
  638. // Toggle speech output.
  639. //
  640. SpeechOutput: function() {
  641. Explorer.Reset();
  642. var speechItems = ['Subtitles', 'Generation'];
  643. speechItems.forEach(
  644. function(x) {
  645. var item = MathJax.Menu.menu.FindId('Accessibility', 'Explorer', x);
  646. if (item) {
  647. item.disabled = !item.disabled;
  648. }});
  649. Explorer.Regenerate();
  650. },
  651. //
  652. // Regenerates speech.
  653. //
  654. Regenerate: function() {
  655. for (var i = 0, all = MathJax.Hub.getAllJax(), jax; jax = all[i]; i++) {
  656. var math = document.getElementById(jax.inputID + '-Frame');
  657. if (math) {
  658. math.removeAttribute('hasSpeech');
  659. Explorer.AddSpeech(math);
  660. }
  661. }
  662. },
  663. Startup: function() {
  664. var Collapsible = MathJax.Extension.collapsible;
  665. if (Collapsible) Collapsible.Dependent(Assistive);
  666. Assistive.addDefaults();
  667. }
  668. };
  669. MathJax.Hub.Register.StartupHook('End Extensions', function() {
  670. Assistive[SETTINGS.explorer === false ? 'Disable' : 'Enable']();
  671. MathJax.Hub.Startup.signal.Post('Explorer Ready');
  672. MathJax.Hub.Register.StartupHook('MathMenu Ready', function() {
  673. COOKIE = MathJax.Menu.cookie;
  674. var Switch = function(menu) {
  675. Assistive[SETTINGS.explorer ? 'Enable' : 'Disable'](true, true);
  676. MathJax.Menu.saveCookie();
  677. };
  678. var ITEM = MathJax.Menu.ITEM,
  679. MENU = MathJax.Menu.menu;
  680. var reset = {action: Explorer.Reset};
  681. var speech = {action: Assistive.speechOption};
  682. var explorerMenu =
  683. ITEM.SUBMENU(['Explorer', 'Explorer'],
  684. ITEM.CHECKBOX(['Active', 'Active'], 'explorer', {action: Switch}),
  685. ITEM.RULE(),
  686. ITEM.SUBMENU(['Walker', 'Walker'],
  687. ITEM.RADIO(['nowalker', 'No walker'], 'Assistive-walker', {value:"none"}),
  688. ITEM.RADIO(['syntactic', 'Syntax walker'], 'Assistive-walker'),
  689. ITEM.RADIO(['semantic', 'Semantic walker'], 'Assistive-walker')
  690. ),
  691. ITEM.SUBMENU(['Highlight', 'Highlight'],
  692. ITEM.RADIO(['none', 'None'], 'Assistive-highlight', reset),
  693. ITEM.RADIO(['hover', 'Hover'], 'Assistive-highlight', reset),
  694. ITEM.RADIO(['flame', 'Flame'], 'Assistive-highlight', reset)
  695. ),
  696. ITEM.SUBMENU(['Background', 'Background'],
  697. ITEM.RADIO(['blue', 'Blue'], 'Assistive-background', reset),
  698. ITEM.RADIO(['red', 'Red'], 'Assistive-background', reset),
  699. ITEM.RADIO(['green', 'Green'], 'Assistive-background', reset),
  700. ITEM.RADIO(['yellow', 'Yellow'], 'Assistive-background', reset),
  701. ITEM.RADIO(['cyan', 'Cyan'], 'Assistive-background', reset),
  702. ITEM.RADIO(['magenta', 'Magenta'], 'Assistive-background', reset),
  703. ITEM.RADIO(['white', 'White'], 'Assistive-background', reset),
  704. ITEM.RADIO(['black', 'Black'], 'Assistive-background', reset)
  705. ),
  706. ITEM.SUBMENU(['Foreground', 'Foreground'],
  707. ITEM.RADIO(['black', 'Black'], 'Assistive-foreground', reset),
  708. ITEM.RADIO(['white', 'White'], 'Assistive-foreground', reset),
  709. ITEM.RADIO(['magenta', 'Magenta'], 'Assistive-foreground', reset),
  710. ITEM.RADIO(['cyan', 'Cyan'], 'Assistive-foreground', reset),
  711. ITEM.RADIO(['yellow', 'Yellow'], 'Assistive-foreground', reset),
  712. ITEM.RADIO(['green', 'Green'], 'Assistive-foreground', reset),
  713. ITEM.RADIO(['red', 'Red'], 'Assistive-foreground', reset),
  714. ITEM.RADIO(['blue', 'Blue'], 'Assistive-foreground', reset)
  715. ),
  716. ITEM.RULE(),
  717. ITEM.CHECKBOX(['SpeechOutput', 'Speech Output'],
  718. 'Assistive-speech', {action: Explorer.SpeechOutput}),
  719. ITEM.CHECKBOX(['Subtitles', 'Subtitles'], 'Assistive-subtitle',
  720. {disabled: !SETTINGS['Assistive-speech']}),
  721. ITEM.SUBMENU(['Generation', 'Generation'],
  722. {disabled: !SETTINGS['Assistive-speech']},
  723. ITEM.RADIO(['eager', 'Eager'], 'Assistive-generation',
  724. {action: Explorer.Regenerate}),
  725. ITEM.RADIO(['mixed', 'Mixed'], 'Assistive-generation',
  726. {action: Explorer.Regenerate}),
  727. ITEM.RADIO(['lazy', 'Lazy'], 'Assistive-generation',
  728. {action: Explorer.Regenerate})
  729. ),
  730. ITEM.RULE(),
  731. ITEM.SUBMENU(['Mathspeak', 'Mathspeak Rules'],
  732. ITEM.RADIO(['mathspeak-default', 'Verbose'],
  733. 'Assistive-ruleset', speech),
  734. ITEM.RADIO(['mathspeak-brief', 'Brief'], 'Assistive-ruleset', speech),
  735. ITEM.RADIO(['mathspeak-sbrief', 'Superbrief'],
  736. 'Assistive-ruleset', speech)
  737. ),
  738. ITEM.SUBMENU(['Chromevox', 'ChromeVox Rules'],
  739. ITEM.RADIO(['chromevox-default', 'Verbose'],
  740. 'Assistive-ruleset', speech),
  741. ITEM.RADIO(['chromevox-short', 'Short'], 'Assistive-ruleset',
  742. speech),
  743. ITEM.RADIO(['chromevox-alternative', 'Alternative'],
  744. 'Assistive-ruleset', speech)
  745. )
  746. );
  747. var submenu = (MENU.FindId('Accessibility') || {}).submenu, index;
  748. if (submenu) {
  749. index = submenu.IndexOfId('Explorer');
  750. if (index !== null) {
  751. submenu.items[index] = explorerMenu;
  752. } else {
  753. index = submenu.IndexOfId('CollapsibleMath');
  754. submenu.items.splice(index + 1, 0, explorerMenu);
  755. }
  756. } else {
  757. index = MENU.IndexOfId('CollapsibleMath');
  758. MENU.items.splice(index + 1, 0, explorerMenu);
  759. }
  760. if (!SETTINGS.explorer) Assistive.DisableMenus(true);
  761. },20); // Between collapsible and auto-collapse extensions
  762. },20);
  763. });
  764. //
  765. // Patch problem with SVG getJaxForMath when used from explorer
  766. // (can be removed after the next release of MathJax).
  767. //
  768. MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
  769. MathJax.Hub.Config({SVG: {addMMLclasses: true}});
  770. var SVG = MathJax.OutputJax.SVG;
  771. if (parseFloat(SVG.version) < 2.7) {
  772. var JAXFROMMATH = SVG.getJaxFromMath;
  773. SVG.Augment({
  774. getJaxFromMath: function (math) {
  775. if (math.parentNode.className.match(/MathJax_SVG_Display/)) math = math.parentNode;
  776. return JAXFROMMATH.call(this,math);
  777. }
  778. });
  779. }
  780. });
  781. //
  782. // Set up the a11y path,if it isn't already in place
  783. //
  784. if (!MathJax.Ajax.config.path.a11y) {
  785. MathJax.Ajax.config.path.a11y = MathJax.Hub.config.root + "/extensions/a11y";
  786. }
  787. MathJax.Ajax.Require('[a11y]/collapsible.js');
  788. MathJax.Hub.Register.StartupHook('Collapsible Ready', function() {
  789. MathJax.Extension.explorer.Explorer.Startup();
  790. MathJax.Ajax.loadComplete('[a11y]/explorer.js');
  791. });