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