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 Sends Braille commands to the Braille API.
7 */
8
9goog.provide('cvox.BrailleBackground');
10
11goog.require('cvox.AbstractBraille');
12goog.require('cvox.BrailleDisplayManager');
13goog.require('cvox.BrailleInputHandler');
14goog.require('cvox.BrailleKeyEvent');
15goog.require('cvox.BrailleTable');
16goog.require('cvox.ChromeVox');
17goog.require('cvox.LibLouis');
18
19
20/**
21 * @constructor
22 * @param {cvox.BrailleDisplayManager=} opt_displayManagerForTest
23 *        Display manager (for mocking in tests).
24 * @param {cvox.BrailleInputHandler=} opt_inputHandlerForTest Input handler
25 *        (for mocking in tests).
26 * @extends {cvox.AbstractBraille}
27 */
28cvox.BrailleBackground = function(opt_displayManagerForTest,
29                                  opt_inputHandlerForTest) {
30  goog.base(this);
31  /**
32   * @type {cvox.BrailleDisplayManager}
33   * @private
34   */
35  this.displayManager_ = opt_displayManagerForTest ||
36      new cvox.BrailleDisplayManager();
37  cvox.BrailleTable.getAll(goog.bind(function(tables) {
38    /**
39     * @type {!Array.<cvox.BrailleTable.Table>}
40     * @private
41     */
42    this.tables_ = tables;
43    this.initialize_(0);
44  }, this));
45  this.displayManager_.setCommandListener(
46      goog.bind(this.onBrailleKeyEvent_, this));
47  /**
48   * @type {cvox.NavBraille}
49   * @private
50   */
51  this.lastContent_ = null;
52  /**
53   * @type {?string}
54   * @private
55   */
56  this.lastContentId_ = null;
57  /**
58   * @type {!cvox.BrailleInputHandler}
59   * @private
60   */
61  this.inputHandler_ = opt_inputHandlerForTest ||
62      new cvox.BrailleInputHandler();
63  this.inputHandler_.init();
64};
65goog.inherits(cvox.BrailleBackground, cvox.AbstractBraille);
66
67
68/** @override */
69cvox.BrailleBackground.prototype.write = function(params) {
70  this.lastContentId_ = null;
71  this.lastContent_ = null;
72  this.inputHandler_.onDisplayContentChanged(params.text);
73  this.displayManager_.setContent(
74      params, this.inputHandler_.getExpansionType());
75};
76
77
78/** @override */
79cvox.BrailleBackground.prototype.setCommandListener = function(func) {
80  // TODO(plundblad): Implement when the background page handles commands
81  // as well.
82};
83
84
85/**
86 * Refreshes the braille translator used for output.  This should be
87 * called when something changed (such as a preference) to make sure that
88 * the correct translator is used.
89 */
90cvox.BrailleBackground.prototype.refreshTranslator = function() {
91  if (!this.liblouis_) {
92    return;
93  }
94  // First, see if we have a braille table set previously.
95  var tables = this.tables_;
96  var table = cvox.BrailleTable.forId(tables, localStorage['brailleTable']);
97  if (!table) {
98    // Match table against current locale.
99    var currentLocale = chrome.i18n.getMessage('@@ui_locale').split(/[_-]/);
100    var major = currentLocale[0];
101    var minor = currentLocale[1];
102    var firstPass = tables.filter(function(table) {
103      return table.locale.split(/[_-]/)[0] == major;
104    });
105    if (firstPass.length > 0) {
106      table = firstPass[0];
107      if (minor) {
108        var secondPass = firstPass.filter(function(table) {
109          return table.locale.split(/[_-]/)[1] == minor;
110        });
111        if (secondPass.length > 0) {
112          table = secondPass[0];
113        }
114      }
115    }
116  }
117  if (!table) {
118    table = cvox.BrailleTable.forId(tables, 'en-US-comp8');
119  }
120  // TODO(plundblad): ONly update when user explicitly chooses a table
121  // so that switching locales changes table by default.
122  localStorage['brailleTable'] = table.id;
123  if (table.dots == '6') {
124    localStorage['brailleTableType'] = 'brailleTable6';
125    localStorage['brailleTable6'] = table.id;
126  } else {
127    localStorage['brailleTableType'] = 'brailleTable8';
128    localStorage['brailleTable8'] = table.id;
129  }
130
131  // Initialize all other defaults.
132  // TODO(plundblad): Stop doing this here.
133  if (!localStorage['brailleTable6']) {
134    localStorage['brailleTable6'] = 'en-US-g1';
135  }
136  if (!localStorage['brailleTable8']) {
137    localStorage['brailleTable8'] = 'en-US-comp8';
138  }
139
140  // If the user explicitly set an 8 dot table, use that when looking
141  // for an uncontracted table.  Otherwise, use the current table and let
142  // getUncontracted find an appropriate corresponding table.
143  var table8Dot = cvox.BrailleTable.forId(tables,
144                                          localStorage['brailleTable8']);
145  var uncontractedTable = cvox.BrailleTable.getUncontracted(
146      tables,
147      table8Dot ? table8Dot : table);
148  this.liblouis_.getTranslator(table.fileNames, goog.bind(
149      function(translator) {
150        if (uncontractedTable.id === table.id) {
151          this.displayManager_.setTranslator(translator);
152          this.inputHandler_.setTranslator(translator);
153        } else {
154          this.liblouis_.getTranslator(uncontractedTable.fileNames, goog.bind(
155              function(uncontractedTranslator) {
156                this.displayManager_.setTranslator(
157                    translator, uncontractedTranslator);
158                this.inputHandler_.setTranslator(
159                    translator, uncontractedTranslator);
160              }, this));
161        }
162      }, this));
163};
164
165
166/**
167 * Called when a Braille message is received from a page content script.
168 * @param {Object} msg The Braille message.
169 */
170cvox.BrailleBackground.prototype.onBrailleMessage = function(msg) {
171  if (msg['action'] == 'write') {
172    this.lastContentId_ = msg['contentId'];
173    this.lastContent_ = cvox.NavBraille.fromJson(msg['params']);
174    this.inputHandler_.onDisplayContentChanged(this.lastContent_.text);
175    this.displayManager_.setContent(
176        this.lastContent_, this.inputHandler_.getExpansionType());
177  }
178};
179
180
181/**
182 * @return {cvox.LibLouis} The liblouis instance used by this object, or null
183 * if not initialized yet.
184 */
185cvox.BrailleBackground.prototype.getLibLouisForTest = function() {
186  return this.liblouis_;
187};
188
189
190/**
191 * Initialization to be done after part of the background page's DOM has been
192 * constructed. Currently only used on ChromeOS.
193 * @param {number} retries Number of retries.
194 * @private
195 */
196cvox.BrailleBackground.prototype.initialize_ = function(retries) {
197  if (retries > 5) {
198    console.error(
199        'Timeout waiting for document.body; not initializing braille.');
200    return;
201  }
202  if (!document.body) {
203    window.setTimeout(goog.bind(this.initialize_, this, ++retries), 500);
204  } else {
205    /**
206     * @type {cvox.LibLouis}
207     * @private
208     */
209    this.liblouis_ = new cvox.LibLouis(
210        chrome.extension.getURL(
211            'chromevox/background/braille/liblouis_nacl.nmf'),
212        chrome.extension.getURL(
213            'chromevox/background/braille/tables'));
214    this.liblouis_.attachToElement(document.body);
215    this.refreshTranslator();
216  }
217};
218
219
220/**
221 * Handles braille key events by dispatching either to the input handler or
222 * a content script.
223 * @param {!cvox.BrailleKeyEvent} brailleEvt The event.
224 * @param {cvox.NavBraille} content Content of display when event fired.
225 * @private
226 */
227cvox.BrailleBackground.prototype.onBrailleKeyEvent_ = function(
228    brailleEvt, content) {
229  if (this.inputHandler_.onBrailleKeyEvent(brailleEvt)) {
230    return;
231  }
232  this.sendCommand_(brailleEvt, content);
233};
234
235
236/**
237 * Dispatches braille input commands to the content script.
238 * @param {!cvox.BrailleKeyEvent} brailleEvt The event.
239 * @param {cvox.NavBraille} content Content of display when event fired.
240 * @private
241 */
242cvox.BrailleBackground.prototype.sendCommand_ =
243    function(brailleEvt, content) {
244  var msg = {
245    'message': 'BRAILLE',
246    'args': brailleEvt
247  };
248  if (content === this.lastContent_) {
249    msg.contentId = this.lastContentId_;
250  }
251  cvox.ExtensionBridge.send(msg);
252};
253