Switch TestFrameNavigationObserver to DidCommitProvisionalLoadForFrame.
[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.
48 * @param {!i18n.input.chrome.inputview.ReadyState} readyState .
49 * @extends {goog.events.EventTarget}
50 * @constructor
52 i18n.input.chrome.inputview.Adapter = function(readyState) {
53 goog.base(this);
55 /**
56 * Whether the keyboard is visible.
58 * @type {boolean}
60 this.isVisible = !document.webkitHidden;
62 /**
63 * The modifier state map.
65 * @type {!Object.<i18n.input.chrome.inputview.StateType, boolean>}
66 * @private
68 this.modifierState_ = {};
71 /**
72 * Tracker for which FeatureName are enabled.
74 * @type {!FeatureTracker};
76 this.features = new FeatureTracker();
79 /**
80 * The system ready state.
82 * @private {!i18n.input.chrome.inputview.ReadyState}
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}
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.
149 * @private {boolean}
151 Adapter.prototype.isBgControllerSwitching_ = false;
155 * Callback for updating settings.
157 * @param {!Object} message .
158 * @private
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.
178 Adapter.prototype.setModifierState = function(stateType, enable) {
179 this.modifierState_[stateType] = enable;
184 * Clears the modifier states.
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 .
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)
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 .
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 .
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
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
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;
291 if (ctrl || alt) {
292 key = '';
294 var result = {
295 'type': type,
296 'key': key,
297 'code': code,
298 'keyCode': opt_keyCode || 0,
299 'spatialData': opt_spatialData
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
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} .
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 .
343 Adapter.prototype.sendHwtRequest = function(payload) {
344 chrome.runtime.sendMessage(goog.object.create(
345 Name.TYPE, Type.HWT_REQUEST, Name.MSG, payload
351 * True if it is a password box.
353 * @return {boolean} .
355 Adapter.prototype.isPasswordBox = function() {
356 return this.contextType == 'password';
361 * True to enable gesture deletion.
363 * @return {boolean}
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;
371 return this.features.isEnabled(FeatureName.GESTURE_DELETION);
376 * True to enable gesture typing.
378 * @return {boolean}
380 Adapter.prototype.isGestureTypingEnabled = function() {
381 return this.features.isEnabled(FeatureName.GESTURE_TYPING);
386 * Callback when blurs in the context.
388 * @private
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.
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);
412 * Sets the current context URL.
414 * @param {string} url .
415 * @private
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));
427 * Returns whether the current context is Google Documents.
429 * @return {boolean} .
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
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
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.
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));
485 this.isChromeVoxOn = details['value'];
486 }).bind(this));
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;
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);
524 this.maybeDispatchSettingsReadyEvent_();
529 * Dispatch event SETTINGS_READY if all required bits are flipped.
531 * @private
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]);
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);
553 * Gets the currently activated input method.
555 * @param {function(string)} callback .
557 Adapter.prototype.getCurrentInputMethod = function(callback) {
558 if (window.inputview && inputview.getCurrentInputMethod) {
559 inputview.getCurrentInputMethod(callback);
560 } else {
561 callback('DU');
567 * Gets the list of all activated input methods.
569 * @param {function(Array.<Object>)} callback .
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}]);
583 * Switches to the input method with id equals |inputMethodId|.
585 * @param {!string} inputMethodId .
587 Adapter.prototype.switchToInputMethod = function(inputMethodId) {
588 if (window.inputview && inputview.switchToInputMethod) {
589 inputview.switchToInputMethod(inputMethodId);
595 * Callback for visibility change on the input view window.
597 * @private
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 .
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;
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 .
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 .
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 .
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
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.
676 Adapter.prototype.hideKeyboard = function() {
677 chrome.input.ime.hideInputView();
682 * Sends DOUBLE_CLICK_ON_SPACE_KEY message.
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.
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
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;
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;
752 * Sends the voice state to background.
754 * @param {boolean} state .
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}
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.
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
799 Adapter.prototype.unsetController = function() {
800 chrome.runtime.sendMessage(
801 goog.object.create(
802 Name.TYPE,
803 Type.UNSET_CONTROLLER));
805 }); // goog.scope