Update mojo sdk to rev 1dc8a9a5db73d3718d99917fadf31f5fb2ebad4f
[chromium-blink-merge.git] / third_party / google_input_tools / src / chrome / os / inputview / adapter.js
blobe35250c0615953ab9668a8b22f22c0f5f17cd667
1 // Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
2 // limitations under the License.
3 // See the License for the specific language governing permissions and
4 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5 // distributed under the License is distributed on an "AS-IS" BASIS,
6 // Unless required by applicable law or agreed to in writing, software
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // You may obtain a copy of the License at
11 // you may not use this file except in compliance with the License.
12 // Licensed under the Apache License, Version 2.0 (the "License");
14 goog.provide('i18n.input.chrome.inputview.Adapter');
16 goog.require('goog.events.Event');
17 goog.require('goog.events.EventHandler');
18 goog.require('goog.events.EventTarget');
19 goog.require('goog.events.EventType');
20 goog.require('goog.object');
21 goog.require('i18n.input.chrome.DataSource');
22 goog.require('i18n.input.chrome.inputview.FeatureName');
23 goog.require('i18n.input.chrome.inputview.FeatureTracker');
24 goog.require('i18n.input.chrome.inputview.GlobalFlags');
25 goog.require('i18n.input.chrome.inputview.ReadyState');
26 goog.require('i18n.input.chrome.inputview.StateType');
27 goog.require('i18n.input.chrome.inputview.events.EventType');
28 goog.require('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent');
29 goog.require('i18n.input.chrome.message');
30 goog.require('i18n.input.chrome.message.ContextType');
31 goog.require('i18n.input.chrome.message.Event');
32 goog.require('i18n.input.chrome.message.Name');
33 goog.require('i18n.input.chrome.message.Type');
35 goog.scope(function() {
36 var CandidatesBackEvent = i18n.input.chrome.DataSource.CandidatesBackEvent;
37 var ContextType = i18n.input.chrome.message.ContextType;
38 var FeatureTracker = i18n.input.chrome.inputview.FeatureTracker;
39 var FeatureName = i18n.input.chrome.inputview.FeatureName;
40 var Name = i18n.input.chrome.message.Name;
41 var Type = i18n.input.chrome.message.Type;
45 /**
46  * The adapter for interview.
47  *
48  * @param {!i18n.input.chrome.inputview.ReadyState} readyState .
49  * @extends {goog.events.EventTarget}
50  * @constructor
51  */
52 i18n.input.chrome.inputview.Adapter = function(readyState) {
53   goog.base(this);
55   /**
56    * Whether the keyboard is visible.
57    *
58    * @type {boolean}
59    */
60   this.isVisible = !document.webkitHidden;
62   /**
63    * The modifier state map.
64    *
65    * @type {!Object.<i18n.input.chrome.inputview.StateType, boolean>}
66    * @private
67    */
68   this.modifierState_ = {};
71   /**
72    * Tracker for which FeatureName are enabled.
73    *
74    * @type {!FeatureTracker};
75    */
76   this.features = new FeatureTracker();
79   /**
80    * The system ready state.
81    *
82    * @private {!i18n.input.chrome.inputview.ReadyState}
83    */
84   this.readyState_ = readyState;
86   chrome.runtime.onMessage.addListener(this.onMessage_.bind(this));
88   /** @private {!goog.events.EventHandler} */
89   this.handler_ = new goog.events.EventHandler(this);
90   this.handler_.
91       listen(document, 'webkitvisibilitychange', this.onVisibilityChange_).
92       // When screen rotate, will trigger resize event.
93       listen(window, goog.events.EventType.RESIZE, this.onVisibilityChange_);
95   // Notifies the initial visibility change message to background.
96   this.onVisibilityChange_();
98 goog.inherits(i18n.input.chrome.inputview.Adapter,
99     goog.events.EventTarget);
100 var Adapter = i18n.input.chrome.inputview.Adapter;
104  * URL prefixes of common Google sites.
106  * @enum {string}
107  */
108 Adapter.GoogleSites = {
109   // TODO: Add support for spreadsheets.
110   DOCS: 'https://docs.google.com/document/d'
114 /** @type {boolean} */
115 Adapter.prototype.isA11yMode = false;
118 /** @type {boolean} */
119 Adapter.prototype.isVoiceInputEnabled = true;
122 /** @type {boolean} */
123 Adapter.prototype.showGlobeKey = false;
126 /** @type {string} */
127 Adapter.prototype.contextType = ContextType.DEFAULT;
130 /** @type {string} */
131 Adapter.prototype.screen = '';
134 /** @type {boolean} */
135 Adapter.prototype.isChromeVoxOn = false;
138 /** @type {string} */
139 Adapter.prototype.textBeforeCursor = '';
142 /** @type {boolean} */
143 Adapter.prototype.isQPInputView = true;
147   * Whether the background controller is on switching.
148   *
149   * @private {boolean}
150   */
151 Adapter.prototype.isBgControllerSwitching_ = false;
155  * Callback for updating settings.
157  * @param {!Object} message .
158  * @private
159  */
160 Adapter.prototype.onUpdateSettings_ = function(message) {
161   this.screen = message[Name.SCREEN];
162   this.queryCurrentSite();
163   this.contextType = /** @type {string} */ (message[Name.CONTEXT_TYPE]);
164   // Resets the flag, since when inputview receive the update setting response,
165   // it means the background switching is done.
166   this.isBgControllerSwitching_ = false;
167   this.dispatchEvent(new i18n.input.chrome.message.Event(Type.UPDATE_SETTINGS,
168       message));
173  * Sets the modifier states.
175  * @param {i18n.input.chrome.inputview.StateType} stateType .
176  * @param {boolean} enable True to enable the state, false otherwise.
177  */
178 Adapter.prototype.setModifierState = function(stateType, enable) {
179   this.modifierState_[stateType] = enable;
184  * Clears the modifier states.
185  */
186 Adapter.prototype.clearModifierStates = function() {
187   this.modifierState_ = {};
192  * Simulates to send 'keydown' and 'keyup' event.
194  * @param {string} key
195  * @param {string} code
196  * @param {number=} opt_keyCode The key code.
197  * @param {!Object=} opt_spatialData .
198  * @param {!Object.<{ctrl: boolean, shift: boolean}>=} opt_modifiers .
199  */
200 Adapter.prototype.sendKeyDownAndUpEvent = function(key, code, opt_keyCode,
201     opt_spatialData, opt_modifiers) {
202   this.sendKeyEvent_([
203     this.generateKeyboardEvent_(
204         goog.events.EventType.KEYDOWN,
205         key,
206         code,
207         opt_keyCode,
208         opt_spatialData,
209         opt_modifiers),
210     this.generateKeyboardEvent_(
211         goog.events.EventType.KEYUP,
212         key,
213         code,
214         opt_keyCode,
215         opt_spatialData,
216         opt_modifiers)
217   ]);
222  * Simulates to send 'keydown' event.
224  * @param {string} key
225  * @param {string} code
226  * @param {number=} opt_keyCode The key code.
227  * @param {!Object=} opt_spatialData .
228  */
229 Adapter.prototype.sendKeyDownEvent = function(key, code, opt_keyCode,
230     opt_spatialData) {
231   this.sendKeyEvent_([this.generateKeyboardEvent_(
232       goog.events.EventType.KEYDOWN, key, code, opt_keyCode,
233       opt_spatialData)]);
238  * Simulates to send 'keyup' event.
240  * @param {string} key
241  * @param {string} code
242  * @param {number=} opt_keyCode The key code.
243  * @param {!Object=} opt_spatialData .
244  */
245 Adapter.prototype.sendKeyUpEvent = function(key, code, opt_keyCode,
246     opt_spatialData) {
247   this.sendKeyEvent_([this.generateKeyboardEvent_(
248       goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData)]);
253  * Use {@code chrome.input.ime.sendKeyEvents} to simulate key events.
255  * @param {!Array.<!Object.<string, string|boolean>>} keyData .
256  * @private
257  */
258 Adapter.prototype.sendKeyEvent_ = function(keyData) {
259   chrome.runtime.sendMessage(
260       goog.object.create(Name.TYPE, Type.SEND_KEY_EVENT, Name.KEY_DATA,
261           keyData));
266  * Generates a {@code ChromeKeyboardEvent} by given values.
268  * @param {string} type .
269  * @param {string} key The key.
270  * @param {string} code The code.
271  * @param {number=} opt_keyCode The key code.
272  * @param {!Object=} opt_spatialData .
273  * @param {!Object.<{ctrl: boolean, shift: boolean}>=} opt_modifiers .
274  * @return {!Object.<string, string|boolean>}
275  * @private
276  */
277 Adapter.prototype.generateKeyboardEvent_ = function(
278     type, key, code, opt_keyCode, opt_spatialData, opt_modifiers) {
279   var StateType = i18n.input.chrome.inputview.StateType;
280   var ctrl = !!this.modifierState_[StateType.CTRL];
281   var alt = !!this.modifierState_[StateType.ALT];
282   var shift = !!this.modifierState_[StateType.SHIFT];
284   if (opt_modifiers) {
285     if (opt_modifiers.ctrl)
286       ctrl = opt_modifiers.ctrl;
287     if (opt_modifiers.shift)
288       shift = opt_modifiers.shift;
289   }
291   if (ctrl || alt) {
292     key = '';
293   }
294   var result = {
295     'type': type,
296     'key': key,
297     'code': code,
298     'keyCode': opt_keyCode || 0,
299     'spatialData': opt_spatialData
300   };
302   result['altKey'] = alt;
303   result['ctrlKey'] = ctrl;
304   result['shiftKey'] = shift;
305   result['capsLock'] = !!this.modifierState_[StateType.CAPSLOCK];
307   return result;
312  * Callback when surrounding text is changed.
314  * @param {string} text .
315  * @param {number} anchor .
316  * @param {number} focus .
317  * @private
318  */
319 Adapter.prototype.onSurroundingTextChanged_ = function(text, anchor, focus) {
320   this.textBeforeCursor = text;
321   this.dispatchEvent(new i18n.input.chrome.inputview.events.
322       SurroundingTextChangedEvent(this.textBeforeCursor, anchor, focus));
327  * Gets the context.
329  * @return {string} .
330  */
331 Adapter.prototype.getContext = function() {
332   var matches = this.textBeforeCursor.match(/([a-zA-Z'-Ḁ-ỹÀ-ȳ]+)\s+$/);
333   var text = matches ? matches[1] : '';
334   return text;
339  * Sends request for handwriting.
341  * @param {!Object} payload .
342  */
343 Adapter.prototype.sendHwtRequest = function(payload) {
344   chrome.runtime.sendMessage(goog.object.create(
345       Name.TYPE, Type.HWT_REQUEST, Name.MSG, payload
346       ));
351  * True if it is a password box.
353  * @return {boolean} .
354  */
355 Adapter.prototype.isPasswordBox = function() {
356   return this.contextType == 'password';
361  * True to enable gesture deletion.
363  * @return {boolean}
364  */
365 Adapter.prototype.isGestureDeletionEnabled = function() {
366   // TODO: Omni bar sends wrong anchor/focus when autocompleting
367   // URLs. Re-enable when that is fixed.
368   if (this.contextType == ContextType.URL) {
369     return false;
370   }
371   return this.features.isEnabled(FeatureName.GESTURE_DELETION);
376  * True to enable gesture typing.
378  * @return {boolean}
379  */
380 Adapter.prototype.isGestureTypingEnabled = function() {
381   return this.features.isEnabled(FeatureName.GESTURE_TYPING);
386  * Callback when blurs in the context.
388  * @private
389  */
390 Adapter.prototype.onContextBlur_ = function() {
391   this.contextType = '';
392   this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events.
393       EventType.CONTEXT_BLUR));
398  * Asynchronously queries the current site.
399  */
400 Adapter.prototype.queryCurrentSite = function() {
401   var adapter = this;
402   var criteria = {'active': true, 'lastFocusedWindow': true};
403   if (chrome && chrome.tabs) {
404     chrome.tabs.query(criteria, function(tabs) {
405       tabs[0] && adapter.setCurrentSite_(tabs[0].url);
406     });
407   }
412  * Sets the current context URL.
414  * @param {string} url .
415  * @private
416  */
417 Adapter.prototype.setCurrentSite_ = function(url) {
418   if (url != this.currentSite_) {
419     this.currentSite_ = url;
420     this.dispatchEvent(new goog.events.Event(
421         i18n.input.chrome.inputview.events.EventType.URL_CHANGED));
422   }
427  * Returns whether the current context is Google Documents.
429  * @return {boolean} .
430  */
431 Adapter.prototype.isGoogleDocument = function() {
432   return this.currentSite_ &&
433       this.currentSite_.lastIndexOf(Adapter.GoogleSites.DOCS) === 0;
438  * Callback when focus on a context.
440  * @param {!Object<string, *>} message .
441  * @private
442  */
443 Adapter.prototype.onContextFocus_ = function(message) {
444   // URL might have changed.
445   this.queryCurrentSite();
447   this.contextType = /** @type {string} */ (message[Name.CONTEXT_TYPE]);
448   this.dispatchEvent(new goog.events.Event(
449       i18n.input.chrome.inputview.events.EventType.CONTEXT_FOCUS));
454  * Initializes the communication to background page.
456  * @private
457  */
458 Adapter.prototype.initBackground_ = function() {
459   chrome.runtime.getBackgroundPage((function() {
460     this.isBgControllerSwitching_ = true;
461     chrome.runtime.sendMessage(goog.object.create(
462         Name.TYPE, Type.CONNECT,
463         Name.VISIBILITY, this.isVisible));
464   }).bind(this));
469  * Loads the keyboard settings.
471  * @param {string} languageCode The language code.
472  */
473 Adapter.prototype.initialize = function(languageCode) {
474   if (chrome.accessibilityFeatures &&
475       chrome.accessibilityFeatures.spokenFeedback) {
476     chrome.accessibilityFeatures.spokenFeedback.get({}, (function(details) {
477       this.isChromeVoxOn = details['value'];
478     }).bind(this));
479     chrome.accessibilityFeatures.spokenFeedback.onChange.addListener((function(
480         details) {
481           if (!this.isChromeVoxOn && details['value']) {
482             this.dispatchEvent(new goog.events.Event(
483                 i18n.input.chrome.inputview.events.EventType.REFRESH));
484           }
485           this.isChromeVoxOn = details['value'];
486         }).bind(this));
487   }
489   this.initBackground_();
491   var StateType = i18n.input.chrome.inputview.ReadyState.StateType;
492   if (window.inputview) {
493     inputview.getKeyboardConfig((function(config) {
494       this.isA11yMode = !!config['a11ymode'];
495       this.features.initialize(config);
496       this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
497       this.maybeDispatchSettingsReadyEvent_();
498     }).bind(this));
499     inputview.getInputMethods((function(inputMethods) {
500       // Only show globe key to switching between IMEs when there are more
501       // than one IME.
502       this.showGlobeKey = inputMethods.length > 1;
503       this.readyState_.markStateReady(StateType.IME_LIST_READY);
504       this.maybeDispatchSettingsReadyEvent_();
505     }).bind(this));
506     inputview.getInputMethodConfig((function(config) {
507       this.isQPInputView = !!config['isNewQPInputViewEnabled'] ||
508           !!config['isNewMDInputViewEnabled'];
509       var voiceEnabled = config['isVoiceInputEnabled'];
510       if (goog.isDef(voiceEnabled)) {
511         this.isVoiceInputEnabled = !!voiceEnabled;
512       }
513       i18n.input.chrome.inputview.GlobalFlags.isQPInputView =
514           this.isQPInputView;
515       this.readyState_.markStateReady(StateType.INPUT_METHOD_CONFIG_READY);
516       this.maybeDispatchSettingsReadyEvent_();
517     }).bind(this));
518   } else {
519     this.readyState_.markStateReady(StateType.IME_LIST_READY);
520     this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
521     this.readyState_.markStateReady(StateType.INPUT_METHOD_CONFIG_READY);
522   }
524   this.maybeDispatchSettingsReadyEvent_();
529  * Dispatch event SETTINGS_READY if all required bits are flipped.
531  * @private
532  */
533 Adapter.prototype.maybeDispatchSettingsReadyEvent_ = function() {
534   var StateType = i18n.input.chrome.inputview.ReadyState.StateType;
535   var states = [
536     StateType.KEYBOARD_CONFIG_READY,
537     StateType.IME_LIST_READY,
538     StateType.INPUT_METHOD_CONFIG_READY];
539   var ready = true;
540   for (var i = 0; i < states.length; i++) {
541     ready = ready && this.readyState_.isReady(states[i]);
542   }
543   if (ready) {
544     window.setTimeout((function() {
545       this.dispatchEvent(new goog.events.Event(
546           i18n.input.chrome.inputview.events.EventType.SETTINGS_READY));
547     }).bind(this), 0);
548   }
553  * Gets the currently activated input method.
555  * @param {function(string)} callback .
556  */
557 Adapter.prototype.getCurrentInputMethod = function(callback) {
558   if (window.inputview && inputview.getCurrentInputMethod) {
559     inputview.getCurrentInputMethod(callback);
560   } else {
561     callback('DU');
562   }
567  * Gets the list of all activated input methods.
569  * @param {function(Array.<Object>)} callback .
570  */
571 Adapter.prototype.getInputMethods = function(callback) {
572   if (window.inputview && inputview.getInputMethods) {
573     inputview.getInputMethods(callback);
574   } else {
575     // Provides a dummy IME item to enable IME switcher UI.
576     callback([
577       {'indicator': 'DU', 'id': 'DU', 'name': 'Dummy IME', 'command': 1}]);
578   }
583  * Switches to the input method with id equals |inputMethodId|.
585  * @param {!string} inputMethodId .
586  */
587 Adapter.prototype.switchToInputMethod = function(inputMethodId) {
588   if (window.inputview && inputview.switchToInputMethod) {
589     inputview.switchToInputMethod(inputMethodId);
590   }
595  * Callback for visibility change on the input view window.
597  * @private
598  */
599 Adapter.prototype.onVisibilityChange_ = function() {
600   this.isVisible = !document.webkitHidden;
601   this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.
602       events.EventType.VISIBILITY_CHANGE));
603   chrome.runtime.sendMessage(goog.object.create(
604       Name.TYPE, Type.VISIBILITY_CHANGE,
605       Name.VISIBILITY, !document.webkitHidden,
606       Name.WORKSPACE_HEIGHT, screen.height - window.innerHeight));
611  * Sends request for completion.
613  * @param {string} query .
614  * @param {!Object=} opt_spatialData .
615  */
616 Adapter.prototype.sendCompletionRequest = function(query, opt_spatialData) {
617   var spatialData = {};
618   if (opt_spatialData) {
619     spatialData[Name.SOURCES] = opt_spatialData.sources;
620     spatialData[Name.POSSIBILITIES] = opt_spatialData.possibilities;
621   }
622   chrome.runtime.sendMessage(goog.object.create(Name.TYPE,
623       Type.COMPLETION, Name.TEXT, query, Name.SPATIAL_DATA, spatialData));
628  * Selects the candidate.
630  * @param {!Object} candidate .
631  */
632 Adapter.prototype.selectCandidate = function(candidate) {
633   chrome.runtime.sendMessage(goog.object.create(
634       Name.TYPE, Type.SELECT_CANDIDATE, Name.CANDIDATE, candidate));
639  * Commits the text.
641  * @param {string} text .
642  */
643 Adapter.prototype.commitText = function(text) {
644   chrome.runtime.sendMessage(goog.object.create(
645       Name.TYPE, Type.COMMIT_TEXT, Name.TEXT, text));
650  * Sets the language.
652  * @param {string} language .
653  */
654 Adapter.prototype.setLanguage = function(language) {
655   chrome.runtime.sendMessage(goog.object.create(
656       Name.TYPE, Type.SET_LANGUAGE, Name.LANGUAGE, language));
661  * Callbck when completion is back.
663  * @param {!Object} message .
664  * @private
665  */
666 Adapter.prototype.onCandidatesBack_ = function(message) {
667   var source = message['source'] || '';
668   var candidates = message['candidates'] || [];
669   this.dispatchEvent(new CandidatesBackEvent(source, candidates));
674  * Hides the keyboard.
675  */
676 Adapter.prototype.hideKeyboard = function() {
677   chrome.input.ime.hideInputView();
682  * Sends DOUBLE_CLICK_ON_SPACE_KEY message.
683  */
684 Adapter.prototype.doubleClickOnSpaceKey = function() {
685   chrome.runtime.sendMessage(
686       goog.object.create(
687           Name.TYPE,
688           Type.DOUBLE_CLICK_ON_SPACE_KEY));
693  * Sends message to the background when do internal inputtool switch.
695  * @param {boolean} inputToolValue The value of the language flag.
696  */
697 Adapter.prototype.toggleLanguageState = function(inputToolValue) {
698   chrome.runtime.sendMessage(
699       goog.object.create(
700           Name.TYPE,
701           Type.TOGGLE_LANGUAGE_STATE,
702           Name.MSG,
703           inputToolValue));
708  * Processes incoming message from option page or inputview window.
710  * @param {*} request Message from option page or inputview window.
711  * @param {*} sender Information about the script
712  *     context that sent the message.
713  * @param {function(*): void} sendResponse Function to call to send a response.
714  * @return {boolean|undefined} {@code true} to keep the message channel open in
715  *     order to send a response asynchronously.
716  * @private
717  */
718 Adapter.prototype.onMessage_ = function(request, sender, sendResponse) {
719   var type = request[Name.TYPE];
720   var msg = request[Name.MSG];
721   if (!i18n.input.chrome.message.isFromBackground(type)) {
722     return;
723   }
724   switch (type) {
725     case Type.CANDIDATES_BACK:
726       this.onCandidatesBack_(msg);
727       break;
728     case Type.CONTEXT_FOCUS:
729       this.onContextFocus_(msg);
730       break;
731     case Type.CONTEXT_BLUR:
732       this.onContextBlur_();
733       break;
734     case Type.SURROUNDING_TEXT_CHANGED:
735       this.onSurroundingTextChanged_(request[Name.TEXT],
736           request[Name.ANCHOR],
737           request[Name.FOCUS]);
738       break;
739     case Type.UPDATE_SETTINGS:
740       this.onUpdateSettings_(msg);
741       break;
742     case Type.VOICE_STATE_CHANGE:
743     case Type.HWT_NETWORK_ERROR:
744     case Type.FRONT_TOGGLE_LANGUAGE_STATE:
745       this.dispatchEvent(new i18n.input.chrome.message.Event(type, msg));
746       break;
747   }
752  * Sends the voice state to background.
754  * @param {boolean} state .
755  */
756 Adapter.prototype.sendVoiceViewStateChange = function(state) {
757   chrome.runtime.sendMessage(goog.object.create(
758       Name.TYPE, Type.VOICE_VIEW_STATE_CHANGE, Name.MSG, state));
762 /** @override */
763 Adapter.prototype.disposeInternal = function() {
764   goog.dispose(this.handler_);
766   goog.base(this, 'disposeInternal');
771  * Return the background IME switching state.
773  * @return {boolean}
774  */
775 Adapter.prototype.isSwitching = function() {
776   return this.isBgControllerSwitching_;
781  * Set the inputtool.
783  * @param {string} keyset The keyset.
784  * @param {string} languageCode The language code.
785  */
786 Adapter.prototype.setController = function(keyset, languageCode) {
787   chrome.runtime.sendMessage(
788       goog.object.create(
789           Name.TYPE,
790           Type.SET_CONTROLLER,
791           Name.MSG,
792           {'rawkeyset': keyset, 'languageCode': languageCode}));
797  * Unset the inputtool
798  */
799 Adapter.prototype.unsetController = function() {
800   chrome.runtime.sendMessage(
801       goog.object.create(
802           Name.TYPE,
803           Type.UNSET_CONTROLLER));
805 });  // goog.scope