[sql] Remove _HAS_EXCEPTIONS=0 from build info.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / braille_ime / braille_ime.js
blobc91257446db690655240232958550566b045d3d0
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 'use strict';
7 /**
8 * @fileoverview Braille hardware keyboard input method.
10 * This method is automatically enabled when a braille display is connected
11 * and ChromeVox is turned on. Most of the braille input and editing logic
12 * is located in ChromeVox where the braille translation library is available.
13 * This IME connects to ChromeVox and communicates using messages as follows:
15 * Sent from this IME to ChromeVox:
16 * {type: 'activeState', active: boolean}
17 * {type: 'inputContext', context: InputContext}
18 * Sent on focus/blur to inform ChromeVox of the type of the current field.
19 * In the latter case (blur), context is null.
20 * {type: 'reset'}
21 * Sent when the {@code onReset} IME event fires or uncommitted text is
22 * committed without being triggered by ChromeVox (e.g. because of a
23 * key press).
24 * {type: 'brailleDots', dots: number}
25 * Sent when the user typed a braille cell using the standard keyboard.
26 * ChromeVox treats this similarly to entering braille input using the
27 * braille display.
28 * {type: 'backspace', requestId: string}
29 * Sent when the user presses the backspace key.
30 * ChromeVox must respond with a {@code keyEventHandled} message
31 * with the same request id.
33 * Sent from ChromeVox to this IME:
34 * {type: 'replaceText', contextID: number, deleteBefore: number,
35 * newText: string}
36 * Deletes {@code deleteBefore} characters before the cursor (or selection)
37 * and inserts {@code newText}. {@code contextID} identifies the text field
38 * to apply the update to (no change will happen if focus has moved to a
39 * different field).
40 * {type: 'setUncommitted', contextID: number, text: string}
41 * Stores text for the field identified by contextID to be committed
42 * either as a result of a 'commitUncommitted' message or a by the IME
43 * unhandled key press event. Unlike 'replaceText', this does not send the
44 * uncommitted text to the input field, but instead stores it in the IME.
45 * {type: 'commitUncommitted', contextID: number}
46 * Commits any uncommitted text if it matches the given context ID.
47 * See 'setUncommitted' above.
48 * {type: 'keyEventHandled', requestId: string, result: boolean}
49 * Response to a {@code backspace} message indicating whether the
50 * backspace was handled by ChromeVox or should be allowed to propagate
51 * through the normal event handling pipeline.
54 /**
55 * @constructor
57 var BrailleIme = function() {};
59 BrailleIme.prototype = {
60 /**
61 * Whether to enable extra debug logging for the IME.
62 * @const {boolean}
63 * @private
65 DEBUG: false,
67 /**
68 * ChromeVox extension ID.
69 * @const {string}
70 * @private
72 CHROMEVOX_EXTENSION_ID_: 'mndnfokpggljbaajbnioimlmbfngpief',
74 /**
75 * Name of the port used for communication with ChromeVox.
76 * @const {string}
77 * @private
79 PORT_NAME: 'cvox.BrailleIme.Port',
81 /**
82 * Identifier for the use standard keyboard option used in the menu and
83 * {@code localStorage}. This can be switched on to type braille using the
84 * standard keyboard, or off (default) for the usual keyboard behaviour.
85 * @const {string}
87 USE_STANDARD_KEYBOARD_ID: 'useStandardKeyboard',
89 // State related to the support for typing braille using a standrad
90 // (qwerty) keyboard.
92 /** @private {boolean} */
93 useStandardKeyboard_: false,
95 /**
96 * Braille dots for keys that are currently pressed.
97 * @private {number}
99 pressed_: 0,
102 * Dots that have been pressed at some point since {@code pressed_} was last
103 * {@code 0}.
104 * @private {number}
106 accumulated_: 0,
109 * Bit in {@code pressed_} and {@code accumulated_} that represent
110 * the space key.
111 * @const {number}
113 SPACE: 0x100,
116 * Maps key codes on a standard keyboard to the correspodning dots.
117 * Keys on the 'home row' correspond to the keys on a Perkins-style keyboard.
118 * Note that the mapping below is arranged like the dots in a braille cell.
119 * Only 6 dot input is supported.
120 * @private
121 * @const {Object<number>}
123 CODE_TO_DOT_: {'KeyF': 0x01, 'KeyJ': 0x08,
124 'KeyD': 0x02, 'KeyK': 0x10,
125 'KeyS': 0x04, 'KeyL': 0x20,
126 'Space': 0x100 },
129 * The current engine ID as set by {@code onActivate}, or the empty string if
130 * the IME is not active.
131 * @type {string}
132 * @private
134 engineID_: '',
137 * The port used to communicate with ChromeVox.
138 * @type {Port} port_
139 * @private
141 port_: null,
144 * Uncommitted text and context ID.
145 * @type {?{contextID: number, text: string}}
146 * @private
148 uncommitted_: null,
151 * Registers event listeners in the chrome IME API.
153 init: function() {
154 chrome.input.ime.onActivate.addListener(this.onActivate_.bind(this));
155 chrome.input.ime.onDeactivated.addListener(this.onDeactivated_.bind(this));
156 chrome.input.ime.onFocus.addListener(this.onFocus_.bind(this));
157 chrome.input.ime.onBlur.addListener(this.onBlur_.bind(this));
158 chrome.input.ime.onInputContextUpdate.addListener(
159 this.onInputContextUpdate_.bind(this));
160 chrome.input.ime.onKeyEvent.addListener(this.onKeyEvent_.bind(this),
161 ['async']);
162 chrome.input.ime.onReset.addListener(this.onReset_.bind(this));
163 chrome.input.ime.onMenuItemActivated.addListener(
164 this.onMenuItemActivated_.bind(this));
165 this.connectChromeVox_();
169 * Called by the IME framework when this IME is activated.
170 * @param {string} engineID Engine ID, should be 'braille'.
171 * @private
173 onActivate_: function(engineID) {
174 this.log_('onActivate', engineID);
175 this.engineID_ = engineID;
176 if (!this.port_) {
177 this.connectChromeVox_();
179 this.useStandardKeyboard_ =
180 localStorage[this.USE_STANDARD_KEYBOARD_ID] === String(true);
181 this.accumulated_ = 0;
182 this.pressed_ = 0;
183 this.updateMenuItems_();
184 this.sendActiveState_();
188 * Called by the IME framework when this IME is deactivated.
189 * @param {string} engineID Engine ID, should be 'braille'.
190 * @private
192 onDeactivated_: function(engineID) {
193 this.log_('onDectivated', engineID);
194 this.engineID_ = '';
195 this.sendActiveState_();
199 * Called by the IME framework when a text field receives focus.
200 * @param {InputContext} context Input field context.
201 * @private
203 onFocus_: function(context) {
204 this.log_('onFocus', context);
205 this.sendInputContext_(context);
209 * Called by the IME framework when a text field looses focus.
210 * @param {number} contextID Input field context ID.
211 * @private
213 onBlur_: function(contextID) {
214 this.log_('onBlur', contextID + '');
215 this.sendInputContext_(null);
219 * Called by the IME framework when the current input context is updated.
220 * @param {InputContext} context Input field context.
221 * @private
223 onInputContextUpdate_: function(context) {
224 this.log_('onInputContextUpdate', context);
225 this.sendInputContext_(context);
229 * Called by the system when this IME is active and a key event is generated.
230 * @param {string} engineID Engine ID, should be 'braille'.
231 * @param {!ChromeKeyboardEvent} event The keyboard event.
232 * @private
234 onKeyEvent_: function(engineID, event) {
235 var result = this.processKey_(event);
236 if (result !== undefined)
237 this.keyEventHandled_(event.requestId, event.type, result);
241 * Called when chrome ends the current text input session.
242 * @param {string} engineID Engine ID, should be 'braille'.
243 * @private
245 onReset_: function(engineID) {
246 this.log_('onReset', engineID);
247 this.engineID_ = engineID;
248 this.sendToChromeVox_({type: 'reset'});
252 * Called by the IME framework when a menu item is activated.
253 * @param {string} engineID Engine ID, should be 'braille'.
254 * @param {string} itemID Identifies the menu item.
255 * @private
257 onMenuItemActivated_: function(engineID, itemID) {
258 if (engineID === this.engineID_ &&
259 itemID === this.USE_STANDARD_KEYBOARD_ID) {
260 this.useStandardKeyboard_ = !this.useStandardKeyboard_;
261 localStorage[this.USE_STANDARD_KEYBOARD_ID] =
262 String(this.useStandardKeyboard_);
263 if (!this.useStandardKeyboard_) {
264 this.accumulated_ = 0;
265 this.pressed_ = 0;
267 this.updateMenuItems_();
272 * Outputs a log message to the console, only if {@link BrailleIme.DEBUG}
273 * is set to true.
274 * @param {string} func Name of the caller.
275 * @param {Object|string=} message Message to output.
276 * @private
278 log_: function(func, message) {
279 if (this.DEBUG) {
280 if (typeof(message) !== 'string') {
281 message = JSON.stringify(message);
283 console.log('BrailleIme.' + func + ': ' + message);
288 * Handles a qwerty key on the home row as a braille key.
289 * @param {!ChromeKeyboardEvent} event Keyboard event.
290 * @return {boolean|undefined} Whether the event was handled, or
291 * {@code undefined} if handling was delegated to ChromeVox.
292 * @private
294 processKey_: function(event) {
295 if (!this.useStandardKeyboard_) {
296 return false;
298 if (event.code === 'Backspace' && event.type === 'keydown') {
299 this.pressed_ = 0;
300 this.accumulated_ = 0;
301 this.sendToChromeVox_(
302 {type: 'backspace', requestId: event.requestId});
303 return undefined;
305 var dot = this.CODE_TO_DOT_[event.code];
306 if (!dot || event.altKey || event.ctrlKey || event.shiftKey ||
307 event.capsLock) {
308 this.pressed_ = 0;
309 this.accumulated_ = 0;
310 return false;
312 if (event.type === 'keydown') {
313 this.pressed_ |= dot;
314 this.accumulated_ |= this.pressed_;
315 return true;
316 } else if (event.type === 'keyup') {
317 this.pressed_ &= ~dot;
318 if (this.pressed_ === 0 && this.accumulated_ !== 0) {
319 var dotsToSend = this.accumulated_;
320 this.accumulated_ = 0;
321 if (dotsToSend & this.SPACE) {
322 if (dotsToSend != this.SPACE) {
323 // Can't combine space and actual dot keys.
324 return true;
326 // Space is sent as a blank cell.
327 dotsToSend = 0;
329 this.sendToChromeVox_({type: 'brailleDots', dots: dotsToSend});
331 return true;
333 return false;
337 * Connects to the ChromeVox extension for message passing.
338 * @private
340 connectChromeVox_: function() {
341 if (this.port_) {
342 this.port_.disconnect();
343 this.port_ = null;
345 this.port_ = chrome.runtime.connect(
346 this.CHROMEVOX_EXTENSION_ID_, {name: this.PORT_NAME});
347 this.port_.onMessage.addListener(
348 this.onChromeVoxMessage_.bind(this));
349 this.port_.onDisconnect.addListener(
350 this.onChromeVoxDisconnect_.bind(this));
354 * Handles a message from the ChromeVox extension.
355 * @param {*} message The message from the extension.
356 * @private
358 onChromeVoxMessage_: function(message) {
359 message = /** @type {{type: string}} */ (message);
360 this.log_('onChromeVoxMessage', message);
361 switch (message.type) {
362 case 'replaceText':
363 message =
365 * @type {{contextID: number, deleteBefore: number,
366 * newText: string}}
368 (message);
369 this.replaceText_(message.contextID, message.deleteBefore,
370 message.newText);
371 break;
372 case 'keyEventHandled':
373 message =
374 /** @type {{requestId: string, result: boolean}} */ (message);
375 this.keyEventHandled_(message.requestId, 'keydown', message.result);
376 break;
377 case 'setUncommitted':
378 message =
379 /** @type {{contextID: number, text: string}} */ (message);
380 this.setUncommitted_(message.contextID, message.text);
381 break;
382 case 'commitUncommitted':
383 message =
384 /** @type {{contextID: number}} */ (message);
385 this.commitUncommitted_(message.contextID);
386 break;
387 default:
388 console.error('Unknown message from ChromeVox: ' +
389 JSON.stringify(message));
390 break;
395 * Handles a disconnect event from the ChromeVox side.
396 * @private
398 onChromeVoxDisconnect_: function() {
399 this.port_ = null;
400 this.log_('onChromeVoxDisconnect', chrome.runtime.lastError);
404 * Sends a message to the ChromeVox extension.
405 * @param {Object} message The message to send.
406 * @private
408 sendToChromeVox_: function(message) {
409 if (this.port_) {
410 this.port_.postMessage(message);
415 * Sends the given input context to ChromeVox.
416 * @param {InputContext} context Input context, or null when there's no input
417 * context.
418 * @private
420 sendInputContext_: function(context) {
421 this.sendToChromeVox_({type: 'inputContext', context: context});
425 * Sends the active state to ChromeVox.
426 * @private
428 sendActiveState_: function() {
429 this.sendToChromeVox_({type: 'activeState',
430 active: this.engineID_.length > 0});
434 * Replaces text in the current text field.
435 * @param {number} contextID Context for the input field to replace the
436 * text in.
437 * @param {number} deleteBefore How many characters to delete before the
438 * cursor.
439 * @param {string} toInsert Text to insert at the cursor.
441 replaceText_: function(contextID, deleteBefore, toInsert) {
442 var addText = chrome.input.ime.commitText.bind(
443 null, {contextID: contextID, text: toInsert}, function() {});
444 if (deleteBefore > 0) {
445 var deleteText = chrome.input.ime.deleteSurroundingText.bind(null,
446 {engineID: this.engineID_, contextID: contextID,
447 offset: -deleteBefore, length: deleteBefore}, addText);
448 // Make sure there's no non-zero length selection so that
449 // deleteSurroundingText works correctly.
450 chrome.input.ime.deleteSurroundingText(
451 {engineID: this.engineID_, contextID: contextID,
452 offset: 0, length: 0}, deleteText);
453 } else {
454 addText();
459 * Responds to an asynchronous key event, indicating whether it was handled
460 * or not. If it wasn't handled, any uncommitted text is committed
461 * before sending the response to the IME API.
462 * @param {string} requestId Key event request id.
463 * @param {string} type Type of key event being responded to.
464 * @param {boolean} response Whether the IME handled the event.
466 keyEventHandled_: function(requestId, type, response) {
467 if (!response && type === 'keydown' && this.uncommitted_) {
468 this.commitUncommitted_(this.uncommitted_.contextID);
469 this.sendToChromeVox_({type: 'reset'});
471 chrome.input.ime.keyEventHandled(requestId, response);
475 * Stores uncommitted text that will be committed on any key press or
476 * when {@code commitUncommitted_} is called.
477 * @param {number} contextID of the current field.
478 * @param {string} text to store.
480 setUncommitted_: function(contextID, text) {
481 this.uncommitted_ = {contextID: contextID, text: text};
485 * Commits the last set uncommitted text if it matches the given context id.
486 * @param {number} contextID
488 commitUncommitted_: function(contextID) {
489 if (this.uncommitted_ && contextID === this.uncommitted_.contextID)
490 chrome.input.ime.commitText(this.uncommitted_);
491 this.uncommitted_ = null;
495 * Updates the menu items for this IME.
497 updateMenuItems_: function() {
498 // TODO(plundblad): Localize when translations available.
499 chrome.input.ime.setMenuItems(
500 {engineID: this.engineID_,
501 items: [
503 id: this.USE_STANDARD_KEYBOARD_ID,
504 label: 'Use standard keyboard for braille',
505 style: 'check',
506 visible: true,
507 checked: this.useStandardKeyboard_,
508 enabled: true