| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| if ((typeof mojo === 'undefined') || !mojo.bindingsLibraryInitialized) { |
| loadScript('mojo_bindings'); |
| } |
| mojo.config.autoLoadMojomDeps = false; |
| |
| loadScript('chromeos.ime.mojom.input_engine.mojom'); |
| |
| /** |
| * Empty result to keep Mojo pipe from disconnection. |
| * @type {Promise} |
| * @const |
| */ |
| var IME_CHANNEL_EMPTY_RESULT = Promise.resolve({result: ""}); |
| |
| /** |
| * Empty message to keep Mojo pipe from disconnection. |
| * @type {Uint8Array} |
| * @const |
| */ |
| var IME_CHANNEL_EMPTY_EXTRA = new Uint8Array(0); |
| |
| /* |
| * Represents the js-side of the InputChannel. |
| * Routes calls from IME service to the IME extension. |
| * @implements {chromeos.ime.mojom.InputChannel} |
| */ |
| class ImeExtensionChannel { |
| constructor() { |
| /** |
| * @private @const |
| * @type {!mojo.Binding} |
| * */ |
| this.binding_ = new mojo.Binding(chromeos.ime.mojom.InputChannel, this); |
| |
| /** |
| * @private |
| * @type {chromeos.ime.mojom.InputChannelPtr} |
| */ |
| this.channelPtr_ = undefined; |
| |
| /** |
| * Handler for the text message. |
| * |
| * @private |
| * @type {function(string):string} |
| */ |
| this.textHandler_ = undefined; |
| |
| /** |
| * Handler for the protobuf message. |
| * |
| * @private |
| * @type {function(Uint8Array):Uint8Array} |
| */ |
| this.protobufHandler_ = undefined; |
| } |
| |
| /** |
| * Get a cached bound InterfacePtr for this InputChannel impl. |
| * Create one the ptr if it's not bound yet. |
| * |
| * @return {!chromeos.ime.mojom.InputChannelPtr}. |
| */ |
| getChannelPtr() { |
| return this.binding_.createInterfacePtrAndBind() |
| } |
| |
| /** |
| * Set a handler for processing text message. The handler must return a |
| * nonnull string, otherwise it will lead to disconnection. |
| * |
| * @param {function(string):string} handler. |
| */ |
| onTextMessage(handler) { |
| this.textHandler_ = handler; |
| return this; |
| } |
| |
| /** |
| * Set a handler for processing protobuf message. The handler must return a |
| * nonnull Uint8Array, otherwise it will lead to disconnection. |
| * |
| * @param {function(!Uint8Array):!Uint8Array} handler. |
| */ |
| onProtobufMessage(handler) { |
| this.protobufHandler_ = handler; |
| return this; |
| } |
| |
| /** |
| * Process the text message from a connected input engine. |
| * |
| * @type {function(string):Promise<string>} |
| * @private |
| * @param {string} message |
| * @return {!Promise<string>} result. |
| */ |
| processText(message) { |
| if (this.textHandler_) { |
| return Promise.resolve({result: this.textHandler_(message)}); |
| } |
| return IME_CHANNEL_EMPTY_RESULT; |
| } |
| |
| /** |
| * Process the protobuf message from a connected input engine. |
| * |
| * @type {function(Uint8Array):Promise<Uint8Array>} |
| * @private |
| * @param {!Uint8Array} message |
| * @return {!Promise<!Uint8Array>} |
| */ |
| processMessage(message) { |
| if (this.protobufHandler_) { |
| return Promise.resolve({result: this.protobufHandler_(message)}); |
| } |
| return IME_CHANNEL_EMPTY_RESULT; |
| } |
| |
| /** |
| * Set the error handler when the channel Mojo pipe is disconnected. |
| * |
| * @param {function():void} handler. |
| */ |
| setConnectionErrorHandler(handler) { |
| if (handler) { |
| this.binding_.setConnectionErrorHandler(handler); |
| } |
| } |
| } |
| |
| /* |
| * The main entry point to the IME Mojo service. |
| */ |
| class ImeService { |
| /** @param {!chromeos.ime.mojom.InputEngineManagerPtr} */ |
| constructor(manager) { |
| /** |
| * The IME Mojo service. Allows extension code to fetch an engine instance |
| * implemented in the connected IME service. |
| * @private |
| * @type {!chromeos.ime.mojom.InputEngineManagerPtr} |
| */ |
| this.manager_ = manager; |
| |
| /** |
| * TODO(crbug.com/837156): Build KeepAlive Mojo pipe. |
| * Handle to a KeepAlive service object, which prevents the extension from |
| * being suspended as long as it remains in scope. |
| * @private |
| * @type {boolean} |
| */ |
| this.keepAlive_ = null; |
| |
| /** |
| * An active IME Engine proxy. Allows extension code to make calls on the |
| * connected InputEngine that resides in the IME service. |
| * @private |
| * @type {!chromeos.ime.mojom.InputChannelPtr} |
| */ |
| this.activeEngine_ = null; |
| |
| /** |
| * A to-client channel instance to receive data from the connected Engine |
| * that resides in the IME service. |
| * @private |
| * @type {ImeExtensionChannel} |
| */ |
| this.clientChannel_ = null; |
| } |
| |
| /** @return {boolean} True if there is a connected IME service. */ |
| isConnected() { |
| return this.manager_ && this.manager_.ptr.isBound(); |
| } |
| |
| /** |
| * Set the error handler when the IME Mojo service is disconnected. |
| * |
| * @param {function():void} callback. |
| */ |
| setConnectionErrorHandler(callback) { |
| if (callback && this.isConnected()) { |
| this.manager_.ptr.setConnectionErrorHandler(callback); |
| } |
| } |
| |
| /** |
| * @return {?chromeos.ime.mojom.InputChannelPtr} A bound IME engine instance |
| * or null if no IME Engine is bound. |
| */ |
| getActiveEngine() { |
| if (this.activeEngine_ && this.activeEngine_.ptr.isBound()) { |
| return this.activeEngine_; |
| } |
| return null; |
| } |
| |
| /** |
| * Set a handler for the client delegate to process plain text messages. |
| * |
| * @param {!function(string):string} callback Callback on text message. |
| */ |
| setDelegateTextHandler(callback) { |
| if (this.clientChannel_) { |
| this.clientChannel_.onTextMessage(callback); |
| } |
| } |
| |
| /** |
| * Set a handler for the client delegate to process protobuf messages. |
| * |
| * @param {!function(!Uint8Array):!Uint8Array} callback Callback on protobuf |
| * message. |
| */ |
| setDelegateProtobufHandler(callback) { |
| if (this.clientChannel_) { |
| this.clientChannel_.onProtobufMessage(callback); |
| } |
| } |
| |
| /** |
| * Activates an input method based on its specification. |
| * |
| * @param {string} imeSpec The specification of an IME (e.g. the engine ID). |
| * @param {!Uint8Array} extra The extra data (e.g. initial tasks to run). |
| * @param {function(boolean):void} onConnection The callback function to |
| * invoke when the IME activation is done. |
| * @param {function():void} onConnectionError The callback function to |
| * invoke when the Mojo pipe on the active engine is disconnected. |
| */ |
| activateIME(imeSpec, extra, onConnection, onConnectionError) { |
| if (this.isConnected()) { |
| |
| // TODO(crbug.com/837156): Try to reuse the current engine if possible. |
| // Disconnect the current active engine and make a new one. |
| this.deactivateIME(); |
| this.activeEngine_ = new chromeos.ime.mojom.InputChannelPtr; |
| |
| // Null value will cause a disconnection on the Mojo pipe. |
| extra = extra ? extra : IME_CHANNEL_EMPTY_EXTRA; |
| |
| // Create a client side channel to receive data from service. |
| if (!this.clientChannel_) { |
| this.clientChannel_ = new ImeExtensionChannel(); |
| } |
| |
| this.manager_ |
| .connectToImeEngine( |
| imeSpec, mojo.makeRequest(this.activeEngine_), |
| this.clientChannel_.getChannelPtr(), extra) |
| .then((result) => { |
| const bound = result && result['success']; |
| if (bound && onConnectionError) { |
| this.activeEngine_.ptr.setConnectionErrorHandler( |
| onConnectionError); |
| }; |
| if (onConnection) { |
| onConnection(bound); |
| }; |
| }); |
| } |
| } |
| |
| /** Deactivate the IME engine if it is connected. */ |
| deactivateIME() { |
| if (this.getActiveEngine()) { |
| this.activeEngine_.ptr.reset(); |
| } |
| this.activeEngine_ = null; |
| // TODO(crbug.com/837156): Release client channel? |
| } |
| } |
| |
| (function() { |
| let ptr = new chromeos.ime.mojom.InputEngineManagerPtr; |
| Mojo.bindInterface( |
| chromeos.ime.mojom.InputEngineManager.name, mojo.makeRequest(ptr).handle); |
| exports.$set('returnValue', new ImeService(ptr)); |
| })(); |