/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" interface nsIDOMKeyEvent; interface nsIDOMWindow; interface nsITextInputProcessorCallback; /** * An nsITextInputProcessor instance is associated with a top level widget which * handles native IME. It's associated by calling beginInputTransaction() or * beginInputTransactionForTests(). While an instance has composition, nobody * can steal the rights to make composition on the top level widget. In other * words, if another instance is composing on a top level widget, either * beginInputTransaction() or beginInputTransactionForTests() returns false * (i.e., not throws an exception). * * NOTE: See nsITextInputProcessorCallback.idl for examples of |callback| in * following examples, * * Example #1 JS-IME can start composition like this: * * var TIP = Components.classes["@mozilla.org/text-input-processor;1"]. * createInstance(Components.interfaces.nsITextInputProcessor); * if (!TIP.beginInputTransaction(window, callback)) { * return; // You failed to get the rights to make composition * } * // Create a keyboard event if the following compositionc change is caused * // by a key event. * var keyEvent = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * // Set new composition string first * TIP.setPendingCompositionString("some-words-are-inputted"); * // Set clause information. * TIP.appendClauseToPendingComposition(23, TIP.ATTR_RAW_CLAUSE); * // Set caret position, this is optional. * TIP.setCaretInPendingComposition(23); * // Flush the pending composition * if (!TIP.flushPendingComposition(keyEvent)) { * // If it returns false, it fails to start composition. * return; * } * * Example #2 JS-IME can separate composition string to two or more clauses: * * // Create a keyboard event if the following compositionc change is caused * // by a key event. * var keyEvent = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * // First, set composition string again * TIP.setPendingCompositionString("some-words-are-inputted"); * // Then, if "are" is selected to convert, there are 3 clauses: * TIP.appendClauseToPendingComposition(11, TIP.ATTR_CONVERTED_CLAUSE); * TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE); * TIP.appendClauseToPendingComposition(9, TIP.ATTR_CONVERTED_CLAUSE); * // Show caret at the beginning of the selected clause * TIP.setCaretInPendingComposition(11); * // Flush the pending composition. Note that if there is a composition, * // flushPendingComposition() won't return false. * TIP.flushPendingComposition(keyEvent); * * Example #3 JS-IME can commit composition with specific string with this: * * // Create a keyboard event if the following compositionc change is caused * // by a key event. * var keyEvent1 = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * // First, there is a composition. * TIP.setPendingCompositionString("some-words-directly-inputted"); * TIP.appendClauseToPendingComposition(28, TIP.ATTR_RAW_CLAUSE); * TIP.flushPendingComposition(keyEvent1); * // Create a keyboard event if the following commit composition is caused * // by a key event. * var keyEvent2 = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * // This is useful when user selects a commit string from candidate list UI * // which is provided by JS-IME. * TIP.commitCompositionWith("selected-words-from-candidate-list", keyEvent2); * * Example #4 JS-IME can commit composition with the last composition string * without specifying commit string: * * // Create a keyboard event if the following compositionc change is caused * // by a key event. * var keyEvent1 = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * // First, there is a composition. * TIP.setPendingCompositionString("some-words-will-be-commited"); * TIP.appendClauseToPendingComposition(27, TIP.ATTR_RAW_CLAUSE); * TIP.flushPendingComposition(keyEvent1); * // Create a keyboard event if the following commit is caused by a key * // event. * var keyEvent2 = * new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); * // This is useful when user just type Enter key. * TIP.commitComposition(keyEvent2); * * Example #5 JS-IME can cancel composition with this: * * // Create a keyboard event if the following composition change is caused * // by a key event. * var keyEvent1 = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * // First, there is a composition. * TIP.setPendingCompositionString("some-words-will-be-canceled"); * TIP.appendClauseToPendingComposition(27, TIP.ATTR_RAW_CLAUSE); * TIP.flushPendingComposition(keyEvent1); * // Create a keyboard event if the following canceling composition is * // caused by a key event. * var keyEvent2 = * new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }); * // This is useful when user doesn't want to commit the composition. * // FYI: This is same as TIP.commitCompositionWith("") for now. * TIP.cancelComposition(keyEvent2); * * Example #6 JS-IME can insert text only with commitCompositionWith(): * * // Create a keyboard event if the following inserting text is caused by a * // key event. * var keyEvent1 = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * if (!TIP.beginInputTransaction(window, callback)) { * return; // You failed to get the rights to make composition * } * TIP.commitCompositionWith("Some words", keyEvent1); * * Example #7 JS-IME can start composition explicitly: * * if (!TIP.beginInputTransaction(window, callback)) { * return; // You failed to get the rights to make composition * } * // Create a keyboard event if the following starting composition is caused * // by a key event. * var keyEvent1 = * new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz }); * // If JS-IME don't want to show composing string in the focused editor, * // JS-IME can dispatch only compositionstart event with this. * if (!TIP.startComposition(keyEvent1)) { * // Failed to start composition. * return; * } * // And when user selects a result from UI of JS-IME, commit with it. * // Then, the key event should be null. * TIP.commitCompositionWith("selected-words"); * * Example #8 JS-IME or JS-Keyboard should dispatch key events even during * composition (non-printable key case): * * if (!TIP.beginInputTransaction(window, callback)) { * return; // You failed to get the rights to dispatch key events * } * * // You don't need to specify .keyCode value if it's non-printable key * // because it can be computed from .key value. * // If you specify non-zero value to .keyCode, it'll be used. * var keyEvent = new KeyboardEvent("", { code: "Enter", key: "Enter" }); * if (TIP.keydown(keyEvent)) { * // Handle its default action * } * * // Even if keydown event was consumed, keyup event should be dispatched. * if (TIP.keyup(keyEvent)) { * // Handle its default action * } * * Example #9 JS-IME or JS-Keyboard should dispatch key events even during * composition (printable key case): * * if (!TIP.beginInputTransaction(window, callback)) { * return; // You failed to get the rights to dispatch key events * } * * // You need to specify .keyCode value if it's printable key. * // The rules of .keyCode value is documented in MDN: * // https://developer.mozilla.org/docs/Web/API/KeyboardEvent.keyCode * // * // #1 If the key location is DOM_KEY_LOCATION_NUMPAD and NumLock is * // active, you should specify DOM_VK_NUMPAD[0-9], DOM_VK_MULTIPLY, * // DOM_VK_ADD, DOM_VK_SEPARATOR, DOM_VK_SUBTRACT, DOM_VK_DECIMAL or * // DOM_VK_DIVIDE. * // #2 If the key is Spacebar, use DOM_VK_SPACE. * // * // Following rules are printable keys in DOM_KEY_LOCATION_STANDARD. * // .keyCode value for a key shouldn't be changed by modifier states: * // #1 If the key can input [0-9] with any modifier state (except * // NumLock state), the value should be DOM_VK_[0-9]. * // #2 Otherwise, and if the key inputs an ASCII alphabet with no * // active modifiers, use DOM_VK_[A-Z]. * // #3 Otherwise, and if the key inputs an ASCII alphabet with no * // active modifiers except Shift key state, use DOM_VK_[A-Z] for * // the shifted character. E.g., if a key causes non-alphabet * // character such as "@" or a Unicode character without Shift key * // but "a" is inputted when Shift key is pressed, the proper * // keyCode is DOM_VK_A. * // #4 Otherwise, and if the key inputs another ASCII character with * // no modifier states, use a proper value for the character. E.g., * // if the key inputs "*" without Shift key state, it should be * // DOM_VK_ASTERISK. * // #5 Otherwise, and if the key inputs another ASCII character with * // Shift key state, use a proper value for the character. E.g., * // if a key causes a Unicode character without Shift key but "&" * // is inputted when Shift key is pressed, the proper keyCode is * // DOM_VK_AMPERSAND. * // See above document for the other cases. * // * // NOTE: If the software keyboard is 10-key like simple phone, * // We don't have common rules to decide its .keyCode value. * // Above rules should be used when the JS-Keyboard emulates PC * // keyboard. * // .key value should be inputting character by the key with current * // modifier state. * // .code value should be empty string if the JS-Keyboard isn't emulating * // physical keyboard. Otherwise, use same value with physical keyboard's * // same key. * var keyEvent = new KeyboardEvent("", { code: "KeyA", key: "a", * keyCode: KeyboardEvent.DOM_VK_A }); * if (TIP.keydown(keyEvent)) { * // Handle its default action * } * * // Even if keydown event was consumed, keyup event should be dispatched. * if (TIP.keyup(keyEvent)) { * // Handle its default action * } * * Example #10 JS-Keyboard doesn't need to initialize modifier states at * calling either keydown() or keyup(). * * // Neither beginInputTransaction() nor beginInputTransactionForTests() * // resets modifier state. * if (!TIP.beginInputTransaction(window, callback)) { * return; // You failed to get the rights to dispatch key events * } * * var leftShift = new KeyboardEvent("", { code: "ShiftLeft", key: "Shift" }); * * // This causes following key events will be shifted automatically. * TIP.keydown(leftShift); * * var rightShift = * new KeyboardEvent("", { code: "ShiftRight", key: "Shift" }); * * TIP.keydown(rightShift); * * // keyup of one of shift key doesn't cause inactivating "Shift" state. * TIP.keyup(rightShift); * * // This causes inactivating "Shift" state completely. * TIP.keyup(leftShift); */ [scriptable, builtinclass, uuid(581f6619-76ed-478c-905d-b8e6553a714a)] interface nsITextInputProcessor : nsISupports { /** * Returns true if this instance was dispatched compositionstart but hasn't * dispatched compositionend yet. */ readonly attribute boolean hasComposition; /** * When you create an instance, you must call beginInputTransaction() first * except when you created the instance for automated tests. * * @param aWindow A DOM window. The instance will look for a top * level widget from this. * @param aCallback Callback interface which handles requests to * IME and notifications to IME. This must not be * null. * @return If somebody uses internal text input service for a * composition, this returns false. Otherwise, returns * true. I.e., only your TIP can create composition * when this returns true. If this returns false, * your TIP should wait next chance. */ boolean beginInputTransaction(in nsIDOMWindow aWindow, in nsITextInputProcessorCallback aCallback); /** * When you create an instance for automated test, you must call * beginInputTransaction(), first. See beginInputTransaction() for more * detail of this. * Note that aCallback can be null. If it's null, nsITextInputProcessor * implementation will handle them automatically. */ [optional_argc] boolean beginInputTransactionForTests( in nsIDOMWindow aWindow, [optional] in nsITextInputProcessorCallback aCallback); /** * startComposition() dispatches compositionstart event explicitly. * IME does NOT need to call this typically since compositionstart event * is automatically dispatched by sendPendingComposition() if * compositionstart event hasn't been dispatched yet. If this is called * when compositionstart has already been dispatched, this throws an * exception. * * @param aKeyboardEvent Key event which causes starting composition. * If its type value is "keydown", this method * dispatches only keydown event first. Otherwise, * dispatches keydown first and keyup at last. * @param aKeyFlags See KEY_* constants. * @return Returns true if composition starts normally. * Otherwise, returns false because it might be * canceled by the web application. */ [optional_argc] boolean startComposition([optional] in nsIDOMKeyEvent aKeyboardEvent, [optional] in unsigned long aKeyFlags); /** * Set new composition string. Pending composition will be flushed by * a call of flushPendingComposition(). However, if the new composition * string isn't empty, you need to call appendClauseToPendingComposition() to * fill all characters of aString with one or more clauses before flushing. * Note that if you need to commit or cancel composition, use * commitComposition(), commitCompositionWith() or cancelComposition(). */ void setPendingCompositionString(in DOMString aString); // ATTR_RAW_CLAUSE means that the clause hasn't been selected nor converted // yet. const unsigned long ATTR_RAW_CLAUSE = 0x02; // ATTR_SELECTED_RAW_CLAUSE means that the clause hasn't been converted yet // but is selected for converting to the other string. const unsigned long ATTR_SELECTED_RAW_CLAUSE = 0x03; // ATTR_CONVERTED_CLAUSE means that the clause has already been converted but // is not selected. This does NOT mean that this clause isn't modifiable. const unsigned long ATTR_CONVERTED_CLAUSE = 0x04; // ATTR_SELECTED_CLAUSE means that the clause has already been converted and // is selected. In other words, the clause is being converted. const unsigned long ATTR_SELECTED_CLAUSE = 0x05; /** * Append a clause to the pending composition. * * If you need to fill the pending composition string with a clause, you * should call this once. For example: * appendClauseToPendingComposition(compositionString.length, * ATTR_RAW_CLAUSE); * is enough. If you need to separate the pending composition string to * multiple clauses, you need to call this multiple times. For example, * if your pending composition string has three clauses and the second clause * is being converted: * appendClauseToPendingComposition(firstClauseLength, * ATTR_CONVERTED_CLAUSE); * appendClauseToPendingComposition(secondClauseLength, * ATTR_SELECTED_CLAUSE); * appendClauseToPendingComposition(thirdClauseLength, * ATTR_CONVERTED_CLAUSE); * Note that if sum of aLength mismatches length of the pending composition * string, flushPendingComposition() will throw an exception. I.e., * |firstClauseLength + secondClauseLength + thirdClauseLength| must be * same as the length of pending composition string. * * TODO: Should be able to specify custom clause style. * * @param aLength Length of the clause. * @param aAttribute One of ATTR_* constants. */ void appendClauseToPendingComposition(in unsigned long aLength, in unsigned long aAttribute); /** * Set caret offset in the pending composition string. If you don't need to * show a caret, you don't need to call this. * * @param aOffset Caret offset in the pending composition string. * This must be between 0 and length of the pending * composition string. */ void setCaretInPendingComposition(in unsigned long aOffset); /** * flushPendingComposition() must be called after * setPendingCompositionString() and appendClauseToPendingComposition() * (setCaretInPendingComposition() is optional) are called. * * Note that compositionstart will be automatically dispatched if this is * called when there is no composition. * * Note that if sum of lengths of appended clauses are not same as composition * string or caret offset is larger than the composition string length, this * throws an exception. * * @param aKeyboardEvent Key event which causes the composition string. * If its type value is "keydown", this method * dispatches only keydown event first. Otherwise, * dispatches keydown first and keyup at last. * @param aKeyFlags See KEY_* constants. * @return Returns true if there is a composition already or * starting composition automatically. * Otherwise, i.e., if it cannot start composition * automatically, e.g., canceled by web apps, returns * false. */ [optional_argc] boolean flushPendingComposition( [optional] in nsIDOMKeyEvent aKeyboardEvent, [optional] in unsigned long aKeyFlags); /** * commitComposition() will commit composition with the last composition * string. If there is no composition, this will throw an exception. * * @param aKeyboardEvent Key event which causes the commit composition. * If its type value is "keydown", this method * dispatches only keydown event first. Otherwise, * dispatches keydown first and keyup at last. * @param aKeyFlags See KEY_* constants. */ [optional_argc] void commitComposition([optional] in nsIDOMKeyEvent aKeyboardEvent, [optional] in unsigned long aKeyFlags); /** * commitCompositionWith() will commit composition with the specific string. * If there is no composition, this will start composition and commit it * with the specified string. * * @param aCommitString The string to be committed. * @param aKeyboardEvent Key event which causes the commit composition. * If its type value is "keydown", this method * dispatches only keydown event first. Otherwise, * dispatches keydown first and keyup at last. * @param aKeyFlags See KEY_* constants. * @return Returns true if there is a composition already or * starting composition automatically. * Otherwise, i.e., if it cannot start composition * automatically, e.g., canceled by web apps, returns * false. */ [optional_argc] boolean commitCompositionWith(in DOMString aCommitString, [optional] in nsIDOMKeyEvent aKeyboardEvent, [optional] in unsigned long aKeyFlags); /** * cancelComposition() will cancel composition. This is for now the same as * calling commitComposition(""). However, in the future, this might work * better. If your IME needs to cancel composition, use this instead of * commitComposition(). * * Note that if you tries to cancel composition when there is no composition, * this throws an exception. * * @param aKeyboardEvent Key event which causes the canceling composition. * If its type value is "keydown", this method * dispatches only keydown event first. Otherwise, * dispatches keydown first and keyup at last. * @param aKeyFlags See KEY_* constants. */ [optional_argc] void cancelComposition([optional] in nsIDOMKeyEvent aKeyboardEvent, [optional] in unsigned long aKeyFlags); // Specifying KEY_DEFAULT_PREVENTED can dispatch key events whose // defaultPrevented are true. Note that if this is specified, keypress event // won't be fired. const unsigned long KEY_DEFAULT_PREVENTED = 0x00000001; // If KEY_NON_PRINTABLE_KEY is specified and the .key value isn't valid // key name, the methods will throws an exception. In other words, this // flag prevents to dispatch key events with wrong key values and to cause // such key events input the key values as text. const unsigned long KEY_NON_PRINTABLE_KEY = 0x00000002; // If KEY_FORCE_PRINTABLE_KEY is specified and even if the .key value is a // registered key name, it's treated as inputting text value. const unsigned long KEY_FORCE_PRINTABLE_KEY = 0x00000004; // If KEY_KEEP_KEY_LOCATION_STANDARD is specified when its .location is not // initialized or initialized with 0, the value isn't computed with .code // value. Note that if .location is initialized with non-zero value, // this flag causes throwing an exception. // NOTE: This is not recommended to use except for tests. const unsigned long KEY_KEEP_KEY_LOCATION_STANDARD = 0x00000008; // If KEY_KEEP_KEYCODE_ZERO is specified when its .keyCode is not initialized // or initialized with 0, the value isn't computed with .key value when it // represents non-printable key. Note that if .keyCode is initialized with // non-zero value, this flag causes throwing an exception. const unsigned long KEY_KEEP_KEYCODE_ZERO = 0x00000010; // If KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT is specified when the key event is // a modifier key's, keydown() and keyup() only modifies its modifier state // without dispatching key events. This is useful for testing odd behavior // or emulating legacy API behavior. const unsigned long KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT = 0x00000020; // These values can be used to do bitwise operation with the return value of // the keydown() method. const unsigned long KEYEVENT_NOT_CONSUMED = 0x00000000; const unsigned long KEYDOWN_IS_CONSUMED = 0x00000001; const unsigned long KEYPRESS_IS_CONSUMED = 0x00000002; /** * keydown() may dispatch a keydown event and some keypress events if * preceding keydown event isn't consumed and they are necessary. * Note that even if this is called during composition, key events may not * be dispatched. In this case, this returns false. * * You should initialize at least .key value and .code value of the event. * Additionally, if you try to emulate a printable key, .keyCode value should * be specified if there is proper key value. See the comment of above * example how to decide .keyCode value of a printable key. On the other * hand, .keyCode value is automatically computed when you try to emulate * non-printable key. However, if you try to emulate physical keyboard of * desktop platform, you need to specify proper value explicitly because * the mapping table of this API isn't enough to emulate the behavior of * Gecko for desktop platforms. * * NOTE: Even if this has composition, JS-Keyboard should call keydown() and * keyup(). Although, with the default preferences and normal * conditions, DOM key events won't be fired during composition. * However, they MAY be dispatched for some reasons, e.g., the web * content listens only key events, or if the standard DOM event spec * will be changed in the future. * * @param aKeyboardEvent Must be a keyboard event which should be dispatched * as a keydown event and keypress events. * #1 Note that you don't need to set charCode value * because it's computed from its key value. * #2 If code value is set properly and location value * isn't specified (i.e., 0), the location value will * be guessed from the code value. * #3 Non-defined code names are not allowed. If your * key isn't registered, file a bug. If your key isn't * defined by any standards, use "" (empty string). * #4 .keyCode is guessed from .key value if the key * name is registered and .keyCode isn't initialized. * #5 modifier key states, e.g., .shiftKey, are * ignored. Instead, modifier states are managed by * each instance and set automatically. * @param aKeyFlags Special flags. The values can be some of KEY_* * constants. * @return KEYEVENT_NOT_CONSUMED, if the keydown event nor * the following keypress event(s) are consumed. * KEYDOWN_IS_CONSUMED, if the keydown event is * consumed. No keypress event will be dispatched in * this case. * KEYPRESS_IS_CONSUMED, if the keypress event(s) is * consumed when dispatched. * Note that keypress event is always consumed by * native code for the printable keys (indicating the * default action has been taken). */ [optional_argc] unsigned long keydown(in nsIDOMKeyEvent aKeyboardEvent, [optional] in unsigned long aKeyFlags); /** * Similar to keydown(), but this dispatches only a keyup event. */ [optional_argc] boolean keyup(in nsIDOMKeyEvent aKeyboardEvent, [optional] in unsigned long aKeyFlags); /** * getModifierState() returns modifier state managed by this instance. * * @param aModifier One of modifier key names. This doesn't support * virtual modifiers like "Accel". * @return true if the modifier key is active. Otherwise, * false. */ boolean getModifierState(in DOMString aModifierKey); /** * shareModifierStateOf() makes the instance shares modifier state of * another instance. When this is called, the instance refers the modifier * state of another instance. After that, changes to either this and the * other instance's modifier state is synchronized. * * @param aOther Another instance which will be referred by the * instance. If this is null, the instance restarts * to manage modifier state independently. */ void shareModifierStateOf(in nsITextInputProcessor aOther); }; %{C++ #define TEXT_INPUT_PROCESSOR_CID \ { 0xcaaab47f, 0x1e31, 0x478e, \ { 0x89, 0x19, 0x97, 0x09, 0x04, 0xe9, 0xcb, 0x72 } } #define TEXT_INPUT_PROCESSOR_CONTRACTID \ "@mozilla.org/text-input-processor;1" %}