Remove the old signature of NotificationManager::closePersistent().
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / braille / liblouis.js
blob2d66b84de3d1de4a99338fab1456b9c50a63a7cc
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.
5 /**
6  * @fileoverview JavaScript shim for the liblouis Native Client wrapper.
7  */
9 goog.provide('cvox.LibLouis');
12 /**
13  * Encapsulates a liblouis Native Client instance in the page.
14  * @constructor
15  * @param {string} nmfPath Path to .nmf file for the module.
16  * @param {string=} opt_tablesDir Path to tables directory.
17  */
18 cvox.LibLouis = function(nmfPath, opt_tablesDir) {
19   /**
20    * Path to .nmf file for the module.
21    * @private {string}
22    */
23   this.nmfPath_ = nmfPath;
25   /**
26    * Path to translation tables.
27    * @private {?string}
28    */
29   this.tablesDir_ = goog.isDef(opt_tablesDir) ? opt_tablesDir : null;
31   /**
32    * Native Client <embed> element.
33    * {@code null} when no <embed> is attached to the DOM.
34    * @private {HTMLEmbedElement}
35    */
36   this.embedElement_ = null;
38   /**
39    * Pending RPC callbacks. Maps from message IDs to callbacks.
40    * @private {!Object<string, function(!Object)>}
41    */
42   this.pendingRpcCallbacks_ = {};
44   /**
45    * Next message ID to be used. Incremented with each sent message.
46    * @private {number}
47    */
48   this.nextMessageId_ = 1;
52 /**
53  * Attaches the Native Client wrapper to the DOM as a child of the provided
54  * element, assumed to already be in the document.
55  * @param {!Element} elem Desired parent element of the instance.
56  */
57 cvox.LibLouis.prototype.attachToElement = function(elem) {
58   if (this.isAttached()) {
59     throw Error('Instance already attached');
60   }
62   var embed = document.createElement('embed');
63   embed.src = this.nmfPath_;
64   embed.type = 'application/x-nacl';
65   embed.width = 0;
66   embed.height = 0;
67   if (!goog.isNull(this.tablesDir_)) {
68     embed.setAttribute('tablesdir', this.tablesDir_);
69   }
70   embed.addEventListener('load', goog.bind(this.onInstanceLoad_, this),
71       false /* useCapture */);
72   embed.addEventListener('error', goog.bind(this.onInstanceError_, this),
73       false /* useCapture */);
74   embed.addEventListener('message', goog.bind(this.onInstanceMessage_, this),
75       false /* useCapture */);
76   elem.appendChild(embed);
77   this.embedElement_ = /** @type {!HTMLEmbedElement} */ (embed);
81 /**
82  * Detaches the Native Client instance from the DOM.
83  */
84 cvox.LibLouis.prototype.detach = function() {
85   if (!this.isAttached()) {
86     throw Error('cannot detach unattached instance');
87   }
89   this.embedElement_.parentNode.removeChild(this.embedElement_);
90   this.embedElement_ = null;
91   for (var id in this.pendingRpcCallbacks_) {
92     this.pendingRpcCallbacks_[id]({});
93   }
94   this.pendingRpcCallbacks_ = {};
98 /**
99  * Determines whether the Native Client instance is attached.
100  * @return {boolean} {@code true} if the <embed> element is attached to the DOM.
101  */
102 cvox.LibLouis.prototype.isAttached = function() {
103   return this.embedElement_ !== null;
108  * Returns a translator for the desired table, asynchronously.
109  * This object must be attached to a document when requesting a translator.
110  * @param {string} tableNames Comma separated list of braille table names for
111  *     liblouis.
112  * @param {function(cvox.LibLouis.Translator)} callback
113  *     Callback which will receive the translator, or {@code null} on failure.
114  */
115 cvox.LibLouis.prototype.getTranslator = function(tableNames, callback) {
116   if (!this.isAttached()) {
117     callback(null /* translator */);
118     return;
119   }
120   this.rpc_('CheckTable', { 'table_names': tableNames }, function(reply) {
121     if (reply['success']) {
122       var translator = new cvox.LibLouis.Translator(this, tableNames);
123       callback(translator);
124     } else {
125       callback(null /* translator */);
126     }
127   }.bind(this));
132  * Dispatches a message to the remote end and returns the reply asynchronously.
133  * A message ID will be automatically assigned (as a side-effect).
134  * @param {string} command Command name to be sent.
135  * @param {!Object} message JSONable message to be sent.
136  * @param {function(!Object)} callback Callback to receive the reply.
137  * @private
138  */
139 cvox.LibLouis.prototype.rpc_ =
140     function(command, message, callback) {
141   if (!this.isAttached()) {
142     throw Error('Cannot send RPC: liblouis instance not loaded');
143   }
144   var messageId = '' + this.nextMessageId_++;
145   message['message_id'] = messageId;
146   message['command'] = command;
147   var json = JSON.stringify(message);
148   if (goog.DEBUG) {
149     window.console.debug('RPC -> ' + json);
150   }
151   this.embedElement_.postMessage(json);
152   this.pendingRpcCallbacks_[messageId] = callback;
157  * Invoked when the Native Client instance successfully loads.
158  * @param {Event} e Event dispatched after loading.
159  * @private
160  */
161 cvox.LibLouis.prototype.onInstanceLoad_ = function(e) {
162   window.console.info('loaded liblouis Native Client instance');
167  * Invoked when the Native Client instance fails to load.
168  * @param {Event} e Event dispatched after loading failure.
169  * @private
170  */
171 cvox.LibLouis.prototype.onInstanceError_ = function(e) {
172   window.console.error('failed to load liblouis Native Client instance');
173   this.detach();
178  * Invoked when the Native Client instance posts a message.
179  * @param {Event} e Event dispatched after the message was posted.
180  * @private
181  */
182 cvox.LibLouis.prototype.onInstanceMessage_ = function(e) {
183   if (goog.DEBUG) {
184     window.console.debug('RPC <- ' + e.data);
185   }
186   var message = /** @type {!Object} */ (JSON.parse(e.data));
187   var messageId = message['in_reply_to'];
188   if (!goog.isDef(messageId)) {
189     window.console.warn('liblouis Native Client module sent message with no ID',
190         message);
191     return;
192   }
193   if (goog.isDef(message['error'])) {
194     window.console.error('liblouis Native Client error', message['error']);
195   }
196   var callback = this.pendingRpcCallbacks_[messageId];
197   if (goog.isDef(callback)) {
198     delete this.pendingRpcCallbacks_[messageId];
199     callback(message);
200   }
205  * Braille translator which uses a Native Client instance of liblouis.
206  * @constructor
207  * @param {!cvox.LibLouis} instance The instance wrapper.
208  * @param {string} tableNames Comma separated list of Table names to be passed
209  *     to liblouis.
210  */
211 cvox.LibLouis.Translator = function(instance, tableNames) {
212   /**
213    * The instance wrapper.
214    * @private {!cvox.LibLouis}
215    */
216   this.instance_ = instance;
218   /**
219    * The table name.
220    * @private {string}
221    */
222   this.tableNames_ = tableNames;
227  * Translates text into braille cells.
228  * @param {string} text Text to be translated.
229  * @param {function(ArrayBuffer, Array<number>, Array<number>)} callback
230  *     Callback for result.  Takes 3 parameters: the resulting cells,
231  *     mapping from text to braille positions and mapping from braille to
232  *     text positions.  If translation fails for any reason, all parameters are
233  *     {@code null}.
234  */
235 cvox.LibLouis.Translator.prototype.translate = function(text, callback) {
236   if (!this.instance_.isAttached()) {
237     callback(null /*cells*/, null /*textToBraille*/, null /*brailleToText*/);
238     return;
239   }
240   var message = { 'table_names': this.tableNames_, 'text': text };
241   this.instance_.rpc_('Translate', message, function(reply) {
242     var cells = null;
243     var textToBraille = null;
244     var brailleToText = null;
245     if (reply['success'] && goog.isString(reply['cells'])) {
246       cells = cvox.LibLouis.Translator.decodeHexString_(reply['cells']);
247       if (goog.isDef(reply['text_to_braille'])) {
248         textToBraille = reply['text_to_braille'];
249       }
250       if (goog.isDef(reply['braille_to_text'])) {
251         brailleToText = reply['braille_to_text'];
252       }
253     } else if (text.length > 0) {
254       // TODO(plundblad): The nacl wrapper currently returns an error
255       // when translating an empty string.  Address that and always log here.
256       console.error('Braille translation error for ' + JSON.stringify(message));
257     }
258     callback(cells, textToBraille, brailleToText);
259   });
264  * Translates braille cells into text.
265  * @param {!ArrayBuffer} cells Cells to be translated.
266  * @param {function(?string)} callback Callback for result.
267  */
268 cvox.LibLouis.Translator.prototype.backTranslate =
269     function(cells, callback) {
270   if (!this.instance_.isAttached()) {
271     callback(null /*text*/);
272     return;
273   }
274   if (cells.byteLength == 0) {
275     // liblouis doesn't handle empty input, so handle that trivially
276     // here.
277     callback('');
278     return;
279   }
280   var message = {
281     'table_names': this.tableNames_,
282     'cells': cvox.LibLouis.Translator.encodeHexString_(cells)
283   };
284   this.instance_.rpc_('BackTranslate', message, function(reply) {
285     if (reply['success'] && goog.isString(reply['text'])) {
286       callback(reply['text']);
287     } else {
288       callback(null /* text */);
289     }
290   });
295  * Decodes a hexadecimal string to an {@code ArrayBuffer}.
296  * @param {string} hex Hexadecimal string.
297  * @return {!ArrayBuffer} Decoded binary data.
298  * @private
299  */
300 cvox.LibLouis.Translator.decodeHexString_ = function(hex) {
301   if (!/^([0-9a-f]{2})*$/i.test(hex)) {
302     throw Error('invalid hexadecimal string');
303   }
304   var array = new Uint8Array(hex.length / 2);
305   var idx = 0;
306   for (var i = 0; i < hex.length; i += 2) {
307     array[idx++] = parseInt(hex.substring(i, i + 2), 16);
308   }
309   return array.buffer;
314  * Encodes an {@code ArrayBuffer} in hexadecimal.
315  * @param {!ArrayBuffer} arrayBuffer Binary data.
316  * @return {string} Hexadecimal string.
317  * @private
318  */
319 cvox.LibLouis.Translator.encodeHexString_ = function(arrayBuffer) {
320   var array = new Uint8Array(arrayBuffer);
321   var hex = '';
322   for (var i = 0; i < array.length; i++) {
323     var b = array[i];
324     hex += (b < 0x10 ? '0' : '') + b.toString(16);
325   }
326   return hex;