mathjax.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Implementation of ChromeVox's bridge to MathJax.
7 *
8 */
9
10goog.provide('cvox.ChromeMathJax');
11
12goog.require('cvox.AbstractMathJax');
13goog.require('cvox.ApiImplementation');
14goog.require('cvox.ChromeVox');
15goog.require('cvox.HostFactory');
16goog.require('cvox.ScriptInstaller');
17
18
19/**
20 * @constructor
21 * @extends {cvox.AbstractMathJax}
22 */
23cvox.ChromeMathJax = function() {
24  goog.base(this);
25
26  /**
27   * The port to communicate with the content script.
28   * @type {Port}
29   */
30  this.port = null;
31
32  /**
33   * The next id to use for async callbacks.
34   * @type {number}
35   * @private
36   */
37  this.nextCallbackId_ = 1;
38
39  /**
40   * Map from callback ID to callback function.
41   * @type {Object.<number, Function>}
42   * @private
43   */
44  this.callbackMap_ = {};
45
46  /**
47   * The ids for converted TeX nodes.
48   * @type {number}
49   * @private
50   */
51  this.texNodeId_ = 0;
52
53  this.init();
54};
55goog.inherits(cvox.ChromeMathJax, cvox.AbstractMathJax);
56
57
58/**
59 * Register a callback function in the mapping.
60 * @param {Function} callback The callback function.
61 * @return {number} id The new id.
62 * @private
63 */
64cvox.ChromeMathJax.prototype.registerCallback_ = function(callback) {
65  var id = this.nextCallbackId_;
66  this.nextCallbackId_++;
67  this.callbackMap_[id] = callback;
68  return id;
69};
70
71
72/**
73 * Destructive Retrieval of a callback function from the mapping.
74 * @param {string} idStr The id.
75 * @return {Function} The callback function.
76 * @private
77 */
78cvox.ChromeMathJax.prototype.retrieveCallback_ = function(idStr) {
79  var id = parseInt(idStr, 10);
80  var callback = this.callbackMap_[id];
81  if (callback) {
82    return callback;
83  }
84  return null;
85};
86
87
88/**
89 * Initialise communication with the content script.
90 */
91cvox.ChromeMathJax.prototype.init = function() {
92  window.addEventListener('message', goog.bind(this.portSetup, this), true);
93  var scripts = new Array();
94  scripts.push(cvox.ChromeVox.host.getFileSrc(
95      'chromevox/injected/mathjax_external_util.js'));
96  scripts.push(cvox.ChromeVox.host.getFileSrc('chromevox/injected/mathjax.js'));
97  scripts.push(cvox.ApiImplementation.siteSpecificScriptLoader);
98  cvox.ScriptInstaller.installScript(
99      scripts, 'mathjax', undefined,
100      cvox.ApiImplementation.siteSpecificScriptBase);
101};
102
103
104/**
105 * Destructive Retrieval of a callback function from the mapping.
106 * @param {string} data The command to be sent to the content script.
107 * @param {Function} callback A callback function.
108 * @param {Object.<string, *>=} args Object of arguments.
109 */
110cvox.ChromeMathJax.prototype.postMsg = function(data, callback, args) {
111  args = args || {};
112  var id = this.registerCallback_(callback);
113  var idStr = id.toString();
114  this.port.postMessage({'cmd': data, 'id': idStr, 'args': args});
115};
116
117
118/**
119 * This method is called when the content script receives a message from
120 * the page.
121 * @param {Event} event The DOM event with the message data.
122 * @return {boolean} True if default event processing should continue.
123 */
124cvox.ChromeMathJax.prototype.portSetup = function(event) {
125  if (event.data == 'cvox.MathJaxPortSetup') {
126    this.port = event.ports[0];
127    this.port.onmessage =
128        goog.bind(
129            function(event) {this.dispatchMessage(event.data);},
130            this);
131    return false;
132  }
133  return true;
134};
135
136
137/**
138 * Call the appropriate Cvox function dealing with MathJax return values.
139 * @param {{cmd: string, id: string, args: Object.<string, string>}} message A
140 * message object.
141 */
142cvox.ChromeMathJax.prototype.dispatchMessage = function(message) {
143  var method;
144  var argNames = [];
145  switch (message['cmd']) {
146    case 'NodeMml':
147      method = this.convertMarkupToDom;
148      argNames = ['mathml', 'elementId'];
149    break;
150    case 'Active':
151      method = this.applyBoolean;
152      argNames = ['status'];
153    break;
154  }
155
156  if (!method) {
157    throw 'Unknown MathJax call: ' + message['cmd'];
158  }
159  var callback = this.retrieveCallback_(message['id']);
160  var args = message['args'];
161  if (callback && method) {
162    method.apply(this,
163                 [callback].concat(
164                     argNames.map(function(x) {return args[x];})));
165  }
166};
167
168
169/**
170 * Converts a Boolean string to boolean value and applies a callback function.
171 * @param {function(boolean)} callback A function with one argument.
172 * @param {boolean} bool A truth value.
173  */
174cvox.ChromeMathJax.prototype.applyBoolean = function(
175    callback, bool) {
176  callback(bool);
177};
178
179
180/**
181 * @override
182 */
183cvox.ChromeMathJax.prototype.isMathjaxActive = function(callback) {
184  var retries = 0;
185
186  var fetch = goog.bind(function() {
187    retries++;
188    try {this.postMsg('Active',
189                      function(result) {
190                        if (result) {
191                          callback(result);
192                        } else if (retries < 5) {
193                          setTimeout(fetch, 1000);
194                        }
195                      });
196        } catch (x) {  // Error usually means that the port is not ready yet.
197          if (retries < 5) {
198            setTimeout(fetch, 1000);
199          } else {
200            throw x;
201          }}},
202                        this);
203
204  fetch();
205};
206
207
208/**
209 * @override
210 */
211cvox.ChromeMathJax.prototype.getAllJax = function(callback) {
212  this.postMsg('AllJax', callback);
213};
214
215
216/**
217 * @override
218 */
219cvox.ChromeMathJax.prototype.registerSignal = function(
220    callback, signal) {
221  this.postMsg('RegSig', callback, {sig: signal});
222};
223
224
225/**
226 * @override
227 */
228cvox.ChromeMathJax.prototype.injectScripts = function() {
229  var retries = 0;
230
231  var fetch = goog.bind(
232      function() {
233        retries++;
234        if (this.port) {
235          this.postMsg('InjectScripts', function() {});
236        } else if (retries < 10) {
237          setTimeout(fetch, 500);
238        }
239      },
240      this);
241
242  fetch();
243};
244
245
246/**
247 * @override
248 */
249cvox.ChromeMathJax.prototype.configMediaWiki = function() {
250  this.postMsg('ConfWikipedia', function() { });
251};
252
253
254/**
255 * @override
256 */
257cvox.ChromeMathJax.prototype.getTex = function(callback, tex) {
258  var altText = tex['alt'] || tex['title'];
259  if (altText) {
260    var newId = 'cvoxId-' + this.texNodeId_++;
261    tex.setAttribute('cvoxId', newId);
262    this.postMsg('TexToMml', callback, {alt: altText, id: newId});
263  }
264};
265
266
267/**
268 * @override
269 */
270cvox.ChromeMathJax.prototype.getAsciiMath = function(callback, asciiMathNode) {
271  var altText = asciiMathNode['alt'] || asciiMathNode['title'];
272  if (altText) {
273    var newId = 'cvoxId-' + this.texNodeId_++;
274    asciiMathNode.setAttribute('cvoxId', newId);
275    this.postMsg('AsciiMathToMml', callback, {alt: altText, id: newId});
276  }
277};
278
279
280/** Export platform constructor. */
281cvox.HostFactory.mathJaxConstructor =
282    cvox.ChromeMathJax;
283