blob: e9019a613a205fba76bbc8fb5d4db63471119522 [file] [log] [blame]
// 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));
})();