gecko-dev/editor/docs/IMEHandlingGuide.rst

1092 строки
49 KiB
ReStructuredText

==================
IME handling guide
==================
This document explains how Gecko handles IME.
Introduction
============
IME is an abbreviation of Input Method Editor. This is a technical term from
Windows but these days, this is used on other platforms as well.
IME is a helper application of a user's text input. It handles native key
events before or after focused application (depending on the platform) and
creates a composition string (a.k.a. preedit string), suggests a list of what
the user attempts to input, commits composition string as a selected item off
the list and commits composition string without any conversion. IME is used by
Chinese, Japanese, Korean and Taiwan users for inputting Chinese characters
because the number of them is beyond thousands and cannot be input from the
keyboard directly. However, especially on mobile devices nowadays, IME is also
used for inputting Latin languages like autocomplete. Additionally, IME may be
used for handwriting systems or speech input systems on some platforms.
If IME is available on focused elements, we call that state "enabled". If IME
is not fully available(i.e., user cannot enable IME), we call this state
"disabled".
If IME is enabled but users use direct input mode (e.g., for inputting Latin
characters), we call it "IME is closed". Otherwise, we call it "IME is open".
(FYI: "open" is also called "active" or "turned on". "closed" is also called
"inactive" or "turned off")
So, this document is useful when you're try to fix a bug for text input in
Gecko.
Composition string and clauses
==============================
Typical Japanese IME can input two or more words into a composition string.
When a user converts from Hiragana characters to Chinese characters the
composition string, Japanese IME separates the composition string into multiple
clauses. For example, if a user types "watasinonamaehanakanodesu", it's
converted to Hiragana characters, "わたしのなまえはなかのです", automatically (In
the following screenshots, the composition string has a wavy underline and the
only one clause is called "raw input clause").
.. image:: inputting_composition_string.png
:alt: Screenshot of raw composition string which is inputting Roman
character mode of MS-IME (Japanese)
.. image:: raw_composition_string.png
:alt: Screenshot of raw composition string whose all characters are Hiragana
character (MS-IME, Japanese)
When a user presses ``Convert`` key, Japanese IME separates the composition
string as "わたしの" (my), "なまえは" (name is) and "なかのです" (Nakano). Then,
converts each clause with Chinese characters: "私の", "名前は" and "中野です" (In
the following screenshot each clause is underlined and not connected
adjacently. These clauses are called "converted clause").
.. image:: converted_composition_string.png
:alt: Screenshot of converted composition string (MS-IME, Japanese)
If one or more clauses were not converted as expected, the user can choose one
of the clauses with Arrow keys and look for the expected result form the list
in the drop down menu (In the following screenshot, the clause with the thicker
underline is called "selected clause").
.. image:: candidatewindow.png
:alt: Screenshot of candidate window of MS-IME (Japanese) which converts the
selected clause
Basically, composition string and each clause style is rendered by Gecko. And
the drop down menu is created by IME.
Each clause is represented with selection in the editor. From chrome script,
you can check it with ``nsISelectionController``. In native code, you can
access it with either ``nsISelectionController`` or ``mozilla::SelectionType``
(the latter is recommended because of type safer). And editor sets these IME
selections from ``mozilla::TextRangeType`` which are sent by
``mozilla::WidgetCompositionEvent`` as ``mozilla::TextRangeArray``. The
following table explains the mapping between them.
.. table:: Selection types of each clause of composition string or caret
+------------------------------------------------------------+---------------------------------------+-------------------------+-------------------------+
| |`nsISelectionController`_ |`mozilla::SelectionType`_|`mozilla::TextRangeType`_|
+============================================================+=======================================+=========================+=========================+
|Caret |``SELECTION_NORMAL`` |``eNormal`` |``eCaret`` |
+------------------------------------------------------------+---------------------------------------+-------------------------+-------------------------+
|Raw text typed by the user |``SELECTION_IME_RAW_INPUT`` |``eIMERawClause`` |``eRawClause`` |
+------------------------------------------------------------+---------------------------------------+-------------------------+-------------------------+
|Selected clause of raw text typed by the user |``SELECTION_IME_SELECTEDRAWTEXT`` |``eIMESelectedRawClause``|``eSelectedRawClause`` |
+------------------------------------------------------------+---------------------------------------+-------------------------+-------------------------+
|Converted clause by IME |``SELECTION_IME_CONVERTEDTEXT`` |``eIMEConvertedClause`` |``eConvertedClause`` |
+------------------------------------------------------------+---------------------------------------+-------------------------+-------------------------+
|Selected clause by the user or IME and also converted by IME|``SELECTION_IME_SELECTEDCONVERTEDTEXT``|``eIMESelectedClause`` |``eSelectedClause`` |
+------------------------------------------------------------+---------------------------------------+-------------------------+-------------------------+
Note that typically, "Selected clause of raw text typed by the user" isn't used
because when composition string is already separated to multiple clauses, that
means that the composition string has already been converted by IME at least
once.
.. _nsISelectionController: https://searchfox.org/mozilla-central/source/dom/base/nsISelectionController.idl
.. _mozilla::SelectionType: https://searchfox.org/mozilla-central/source/dom/base/nsISelectionController.idl
.. _mozilla::TextRangeType: https://searchfox.org/mozilla-central/source/widget/TextRange.h
Modules handling IME composition
================================
widget
------
Each widget handles native IME events and dispatches ``WidgetCompositionEvent``
with ``mozilla::widget::TextEventDispatcher`` to represent the behavior of IME
in the focused editor.
This is the only module that depends on the users platform. See also
`Native IME handlers`_ section for the detail of each platform's
implementation.
.. note::
Android widget still does not use ``TextEventDispatcher`` to dispatch
``WidgetCompositionEvents``, see
`bug 1137567 <https://bugzilla.mozilla.org/show_bug.cgi?id=1137567>`__.
mozilla::widget::TextEventDispatcher
------------------------------------
This class is used by native IME handler(s) on each platform. This capsules the
logic to dispatch ``WidgetCompositionEvent`` and ``WidgetKeyboardEvent`` for
making the behavior on each platform exactly same. For example, if
``WidgetKeyboardEvent`` should be dispatched when there is a composition is
managed by this class in XP level. First of use, native IME handlers get the
rights to use ``TextEventDispatcher`` with a call of
``BeginNativeInputTransaction()``. Then, ``StartComposition()``,
``SetPendingComposition()``, ``FlushPendingComposition()``,
``CommitComposition()``, etc. are available if
``BeginNativeInputTransaction()`` return true. These methods automatically
manage composition state and dispatch ``WidgetCompositionEvent`` properly.
This is also used by ``mozilla::TextInputProcessor`` which can emulates (or
implements) IME with chrome script. So, native IME handlers using this class
means that the dispatching part is also tested by automated tests.
mozilla::WidgetCompositionEvent
-------------------------------
Internally, ``WidgetCompositionEvent`` represents native IME behavior. Its
message is one of following values:
eCompositionStart
^^^^^^^^^^^^^^^^^
This is dispatched at starting a composition. This represents a DOM
``compositionstart`` event. The mData value is a selected string at dispatching
the DOM event and it's automatically set by ``TextComposition``.
eCompositionUpdate
^^^^^^^^^^^^^^^^^^
This is dispatched by ``TextComposition`` when an ``eCompositionChange`` will
change the composition string. This represents a DOM ``compositionupdate``
event.
eCompositionEnd
^^^^^^^^^^^^^^^
This is dispatched by ``TextComposition`` when an ``eCompositionCommitAsIs`` or
``eCompositionCommit`` event is dispatched. This represents a DOM
``compositionend`` event.
eCompositionChange
^^^^^^^^^^^^^^^^^^
This is used internally only. This is dispatched at modifying a composition
string, committing a composition, changing caret position and/or changing
ranges of clauses. This represents a DOM text event which is not in any
standards. ``mRanges`` should not be empty only with this message.
eCompositionCommitAsIs
^^^^^^^^^^^^^^^^^^^^^^
This is used internally only. This is dispatched when a composition is
committed with the string. The ``mData`` value should be always be an empty
string. This causes a DOM text event without clause information and a DOM
``compositionend`` event.
eCompositionCommit
^^^^^^^^^^^^^^^^^^
This is used internally only. This is dispatched when a composition is
committed with specific string. The ``mData`` value is the commit string. This
causes a DOM text event without clause information and a DOM ``compositionend``
event.
.. table:: Table of event messages
+--------------------------+-------------------------------------------+-------------------------------+-----------------------+----------------------+
| |meaning of mData |who sets ``mData``? |``mRanges`` |representing DOM event|
+==========================+===========================================+===============================+=======================+======================+
|``eCompositionStart`` |selected string before starting composition|``TextComposition`` |``nullptr`` |``compositionstart`` |
+--------------------------+-------------------------------------------+-------------------------------+-----------------------+----------------------+
|``eCompositionUpdate`` |new composition string |``TextComposition`` |``nullptr`` |``compositionupdate`` |
+--------------------------+-------------------------------------------+-------------------------------+-----------------------+----------------------+
|``eCompositionEnd`` |commit string |``TextComposition`` |``nullptr`` |``compositionend`` |
+--------------------------+-------------------------------------------+-------------------------------+-----------------------+----------------------+
|``eCompositionChange`` |new composition string |widget (or ``TextComposition``)|must not be ``nullptr``|``text`` |
+--------------------------+-------------------------------------------+-------------------------------+-----------------------+----------------------+
|``eCompositionCommitAsIs``|N/A (must be empty) |nobody |``nullptr`` |None |
+--------------------------+-------------------------------------------+-------------------------------+-----------------------+----------------------+
|``eCompositionCommit`` |commit string |widget (or ``TextComposition``)|``nullptr`` |None |
+--------------------------+-------------------------------------------+-------------------------------+-----------------------+----------------------+
PresShell
---------
``PresShell`` receives the widget events and decides an event target from
focused document and element. Then, it sends the events and the event target to
``IMEStateManager``.
mozilla::IMEStateManager
------------------------
``IMEStateManager`` looks for a ``TextComposition`` instance whose native IME
context is same as the widget' which dispatches the widget event. If there is
no proper ``TextComposition`` instance, it creates the instance. And it sends
the event to the ``TextComposition`` instance.
Note that all instances of ``TextComposition`` are managed by
``IMEStateManager``. When an instance is created, it's registered to the list.
When composition completely ends, it's unregistered from the list (and released
automatically).
mozilla::TextComposition
------------------------
``TextComposition`` manages a composition and dispatches DOM
``compositionupdate`` events.
When this receives an ``eCompositionChange``, ``eCompositionCommit`` or
``eCompositionCommitAsIs`` event, it dispatches the event to the stored node
which was the event target of ``eCompositionStart`` event. Therefore, this
class guarantees that all composition events for a composition are fired on
same element.
When this receives ``eCompositionChange`` or ``eCompositionCommit``, this
checks if new composition string (or committing string) is different from the
last data stored by the ``TextComposition``. If the composition event is
changing the composition string, the ``TextComposition`` instance dispatches
``WidgetCompositionEvent`` with ``eCompositionUpdate`` into the DOM tree
directly and modifies the last data. The ``eCompositionUpdate`` event will
cause a DOM ``compositionupdate`` event.
When this receives ``eCompositionCommitAsIs`` or ``eCompositionCommit``, this
dispatches an ``eCompositionEnd`` event which will cause a DOM
``compositionend`` event after dispatching ``eCompositionUpdate`` event and/or
``eCompositionChange`` event if necessary.
One of the other important jobs of this is, when a focused editor handles a
dispatched ``eCompositionChange`` event, this modifies the stored composition
string and its clause information. The editor refers the stored information for
creating or modifying a text node representing a composition string.
And before dispatching ``eComposition*`` events, this class removes ASCII
control characters from dispatching composition event's data in the default
settings. Although, this can be disabled with
``"dom.compositionevent.allow_control_characters"`` pref.
Finally, this class guarantees that requesting to commit or cancel current
composition to IME is perefored synchronously. See
`Forcibly committing composition`_ section for the detail.
editor/libeditor
----------------
`mozilla::EditorEventListener <https://searchfox.org/mozilla-central/source/editor/libeditor/EditorEventListener.cpp>`__
listens for trusted DOM ``compositionstart``, ``text`` and ``compositionend``
events and notifies
`mozilla::EditorBase <https://searchfox.org/mozilla-central/source/editor/libeditor/EditorBase.cpp>`__
and
`mozilla::TextEditor <https://searchfox.org/mozilla-central/source/editor/libeditor/TextEditor.cpp>`__
of the events.
When ``EditorBase`` receives an ``eCompositionStart``
(DOM ``"compositionstart"``) event, it looks for a proper ``TextComposition``
instance and stores it.
When ``TextEditor`` receives an ``eCompositionChange`` (DOM ``"text"``) event,
it creates or modifies a text node which includes the composition string and
`mozilla::CompositionTransaction <https://searchfox.org/mozilla-central/source/editor/libeditor/CompositionTransaction.cpp>`__
(it was called ``IMETextTxn``) sets IME selections for representing the clauses
of the composition string.
When ``EditorBase`` receives an ``eCompositionEnd`` (DOM ``"compositionend"``)
event, it releases the stored ``TextComposition`` instance.
nsTextFrame
-----------
``nsTextFrame`` paints IME selections.
mozilla::IMEContentObserver
---------------------------
``IMEContentObserver`` observes various changes of a focused editor. When a
corresponding element of a ``TextEditor`` or ``HTMLEditor`` instance gets
focus, an instance is created by ``IMEStateManager``, then, starts to observe
and notifies ``widget`` of IME getting focus. When the editor loses focus, it
notifies ``widget`` of IME losing focus and stops observing everything.
Finally, it's destroyed by ``IMEStateManager``.
This class observes selection changes (caret position changes), text changes of
a focused editor and layout changes (by reflow or scroll) of everything in the
document. It depends on the result of ``nsIWidget::GetIMEUpdatePreference()``
what is observed.
When this notifies ``widget`` of something, it needs to be safe to run
script because notifying something may cause dispatching one or more DOM events
and/or new reflow. Therefore, ``IMEContentObserver`` only stores which
notification should be sent to ``widget``. Then,
``mozilla::IMEContentObserver::IMENotificationSender`` tries to send the
pending notifications when it might become safe to do that. Currently, it's
tried:
* after a native event is dispatched from ``PresShell::HandleEventInternal()``
* when new focused editor receives DOM ``focus`` event
* when next refresh driver tick
.. note::
The 3rd timing may not be safe actually, but it causes a lot of oranges of
automated tests.
See also `Notifications to IME`_ section for the detail of sending
notifications.
Currently, ``WidgetQueryContentEvent`` is handled via ``IMEContentObserver``
because if it has a cache of selection, it can set reply of
``eQuerySelectedText`` event only with the cache. That is much faster than
using ``ContentEventHandler``.
e10s support
============
Even when a remote process has focus, native IME handler in chrome process does
its job. So, there is process boundary between native IME handler and focused
editor. Unfortunately, it's not allowed to use synchronous communication from
chrome process to a remote process. This means that chrome process (and also
native IME and our native IME handler) cannot query the focused editor contents
directly. For fixing this issue, we have ``ContentCache`` classes around
process boundary.
mozilla::ContentCache
---------------------
This is a base class of ``ContentCacheInChild`` and ``ContentCacheInParent``
and IPC-aware. This has common members of them including all cache data:
``mText``
Whole text in focused editor. This may be too big but IME may request all
text in the editor.
If we can separate editor contents per paragraph, moving selection between
paragraphs generates pseudo focus move, we can reduce this size and runtime
cost of ``ContentEventHandler``. However, we've not had a plan to do that
yet. Note that Microsoft Word uses this hack.
``mCompositionStart``
Offset of composition string in ``mText``. When there is no composition,
this is ``UINT32_MAX``.
``mSelection::mAnchor``, ``mSelection::mFocus``
Offset of selection anchor and focus in ``mText``.
``mSelection::mWritingMode``
Writing mode at selection start.
``mSelection::mAnchorCharRect``, ``mSelection::mFocusCharRect``
Next character rectangle of ``mSelection::mAnchor`` and
``mSelection::mFocus``. If corresponding offset is end of the editor
contents, its rectangle should be a caret rectangle.
These rectangles shouldn't be empty rect.
``mSelection::mRect``
Unified character rectangle in selection range. When the selection is
collapsed, this should be caret rect.
``mFirstRect``
First character rect of ``mText``. When ``mText`` is empty string, this
should be caret rect.
``mCaret::mOffset``
Always same as selection start offset even when selection isn't collapsed.
``mCaret::mRect``
Caret rect at ``mCaret::mOffset``. If caret isn't actually exists, it's
computed with a character rect at the offset.
``mTextRectArray::mStart``
If there is composition, ``mStart`` is same as ``mCompositionStart``.
Otherwise, ``UINT32_MAX``.
``mTextRectArray::mRects``
Each character rectangle of composition string.
``mEditorRect``
The rect of editor element.
mozilla::ContentCacheInChild
----------------------------
This exists only in remote processes. This is created as a member of
`PuppetWidget <https://searchfox.org/mozilla-central/source/widget/PuppetWidget.cpp>`__.
When ``PuppetWidget`` receives notifications to IME from ``IMEContentObserver``
in the remote process, it makes this class modify its cached content. Then,
this class do that with ``WidgetQueryContentEvents``. Finally, ``PuppetWidget``
sends the notification and ``ContentCacheInParent`` instance as
``ContentCache`` to its parent process.
mozilla::ContentCacheInParent
-----------------------------
This exists as a member of ``TabParent``. When ``TabParent`` receives
notification from corresponding remote process, it assigns
``ContentCacheInParent`` new ``ContentCache`` and post the notification to
``ContentCacheInParent``. If all sent ``WidgetCompositionEvents`` and
``WidgetSelectionEvents`` are already handled in the remote process,
``ContentCacheInParent`` sending the notifications to widget.
And also this handles ``WidgetQueryContentEvents`` with its cache. Supported
event messages of them are:
* ``eQuerySelectedText`` (only with ``SelectionType::eNormal``)
* ``eQueryTextContent``
* ``eQueryTextRect``
* ``eQueryCaretRect``
* ``eQueryEditorRect``
Additionally, this does not support query content events with XP line breakers
but this must not be any problem since native IME handlers query contents with
native line breakers.
``ContentCacheInParent`` also manages sent ``WidgetCompositionEvents`` and
``WidgetSelectionEvents``. After these events are handled in the remote
process, ``TabParent`` receives it with a call of
``RecvOnEventNeedingAckHandled()``. Then, it calls
``ContentCacheInParent::OnEventNeedingAckHandled()``. Finally,
``ContentCacheInParent`` flushes pending notifications.
How do mozilla::TextComposition and mozilla::IMEStateManager work in e10s mode?
-------------------------------------------------------------------------------
In remote process, they work as non-e10s mode. On the other hand, they work
specially in parent process.
When ``IMEStateManager`` in parent process receives ``eCompositionStart``, it
creates ``TextComposition`` instance normally. However, if the event target has
remote contents, ``TextComposition::DispatchCompositionEvent()`` directly sends
the event to the remote process instead of dispatching the event into the
target DOM tree in the process.
That means that even in a parent process, anybody can retrieve
``TextComposition`` instance, but it just does nothing in parent process.
``IMEStateManager`` works more complicated because ``IMEStateManager`` in each
process need to negotiate about owner ship of managing input context.
When a remote process gets focus, temporarily, ``IMEStateManager`` in parent
process disables IME in the widget. After that, ``IMEStateManager`` in the
remote process will set proper input context for the focused editor. At this
time, ``IMEStateManager`` in the parent process does nothing. Therefore,
``IMEContentObserver`` is never created while a remote process has focus.
When a remote process loses focus, ``IMEStateManager`` in parent process
notifies ``IMEStateManager`` in the remote process of
"Stop IME state management". When ``IMEStateManager::StopIMEStateManagement()``
is called in the remote process by this, the ``IMEStateManager`` forgets all
focus information (i.e., that indicates nobody has focus).
When ``IMEStateManager`` in parent process is notified of pseudo focus move
from or to menubar while a remote process has focus, it notifies the remote
process of "Menu keyboard listener installed". Then, ``TabChild`` calls
``IMEStateManager::OnInstalledMenuKeyboardListener()`` in the remote process.
Style of each clause
--------------------
The style of each IME selection is managed by
`LookAndFeel <https://searchfox.org/mozilla-central/source/widget/LookAndFeel.h>`__
class per platform. Therefore, it can be overridden by prefs.
Background color, foreground color (text color) and underline color can be
specified with following prefs. The values must be string of "#rrggbb" format.
* ``ui.IMERawInputBackground``
* ``ui.IMERawInputForeground``
* ``ui.IMERawInputUnderline``
* ``ui.IMESelectedRawTextBackground``
* ``ui.IMESelectedRawTextForeground``
* ``ui.IMESelectedRawTextUnderline``
* ``ui.IMEConvertedTextBackground``
* ``ui.IMEConvertedTextForeground``
* ``ui.IMEConvertedTextUnderline``
* ``ui.IMESelectedConvertedTextBackground``
* ``ui.IMESelectedConvertedTextForeground``
* ``ui.IMESelectedConvertedTextUnderline``
Underline style can be specified with the following prefs. The values are
integer, 0: none, 1: dotted, 2: dashed, 3: solid, 4: double, 5: wavy (The
values same as ``mozilla::StyleTextDecorationStyle`` defined in
`nsStyleConsts.h <https://searchfox.org/mozilla-central/source/layout/style/nsStyleConsts.h>`__).
* ``ui.IMERawInputUnderlineStyle``
* ``ui.IMESelectedRawTextUnderlineStyle``
* ``ui.IMEConvertedTextUnderlineStyle``
* ``ui.IMESelectedConvertedTextUnderlineStyle``
Underline width can be specified with ``"ui.IMEUnderlineRelativeSize"`` pref.
This affects all types of clauses. The value should be 100 or 200. 100 means
normal width, 200 means double width.
On some platforms, IME may support its own style for each clause. Currently,
this feature is supported in TSF mode of Windows and on Linux. The style
information is stored in ``TextRangeStyle`` which is defined in
`TextRange.h <https://searchfox.org/mozilla-central/source/widget/TextRange.h>`__.
It's a member of ``TextRange``. ``TextRange`` is stored in ``mRanges`` of
``WidgetCompositionEvent`` only when its message is ``eCompositionChange``.
Lifetime of composition string
==============================
When native IME notifies Gecko of starting a composition, a widget dispatches
``WidgetCompositionEvent`` with ``eCompositionStart`` which will cause a DOM
``compositionstart`` event.
When native IME notifies Gecko of a composition string change, a caret position
change and/or a change of length of clauses, a widget dispatches
``WidgetCompositionEvent`` with ``eCompositionChange`` event. It will cause a
DOM ``compositionupdate`` event when composition string is changing. That is
dispatched by ``TextComposition`` automatically. After that when the widget and
``PresShell`` of the focused editor have not been destroyed yet, the
``eCompositionChange`` will cause a DOM text event which is not in any web
standards.
When native IME notifies Gecko of the ending of a composition, a widget
dispatches ``WidgetCompositionEvent`` with ``eCompositionCommitAsIs`` or
``eCompositionCommit``. If the committing string is different from the last set
of data (i.e., if the event message is ``eCompositionCommit``),
``TextComposition`` dispatches a DOM ``compositionupdate`` event. After that,
when the widget and ``PresShell`` of the focused editor have not been destroyed
yet, an ``eCompositionChange`` event dispatched by ``TextComposition``, that
causes a DOM text event. Finally, if the widget and PresShell of the focused
editor has not been destroyed yet too, ``TextComposition`` dispatches an
``eCompositionEnd`` event which will cause a DOM compositionend event.
Limitation of handling composition
==================================
Currently, ``EditorBase`` touches undo stack at receiving every
``WidgetCompositionEvent``. Therefore, ``EditorBase`` requests to commit
composition when the following cases occur:
* The editor loses focus
* The caret is moved by mouse or Javascript
* Value of the editor is changed by Javascript
* Node of the editor is removed from DOM tree
* Somethings object is modified in an HTML editor, e.g., resizing an image
* Composition string is moved to a different position which is specified by
native IME (e.g., only a part of composition is committed)
In the future, we should fix this limitation. If we make ``EditorBase`` not
touch undo stack until composition is committed, some of the cases must be
fixed.
Notifications to IME
====================
XP part of Gecko uses ``nsIWidget::NotifyIME()`` for notifying ``widget`` of
something useful to handle IME. Note that some of them are notified only when
``nsIWidget::GetIMEUpdatePreference()`` returns flags which request the
notifications.
``NOTIFY_IME_OF_TEXT_CHANGE``, ``NOTIFY_IME_OF_SELECTION_CHANGE``,
``NOTIFY_IME_OF_POSITION_CHANGE`` and
``NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED`` are always sent by following order:
1. ``NOTIFY_IME_OF_TEXT_CHANGE``
2. ``NOTIFY_IME_OF_SELECTION_CHANGE``
3. ``NOTIFY_IME_OF_POSITION_CHANGE``
4. ``NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED``
If sending one of above notifications causes higher priority notification, the
sender should abort to send remaining notifications and restart from high
priority notification again.
Additionally, all notifications except ``NOTIFY_IME_OF_BLUR`` should be sent
only when it's safe to run script since the notification may cause querying
content and/or dispatching composition events.
NOTIFY_IME_OF_FOCUS
-------------------
When an editable editor gets focus and ``IMEContentObserver`` starts to observe
it, this is sent to widget. This must be called after the previous
``IMEContentObserver`` notified widget of ``NOTIFY_IME_OF_BLUR``.
Note that even if there are pending notifications, they are canceled when
``NOTIFY_IME_OF_FOCUS`` is sent since querying content with following
notifications immediately after getting focus does not make sense. The result
is always same as the result of querying contents at receiving this
notification.
NOTIFY_IME_OF_BLUR
------------------
When an ``IMEContentObserver`` instance ends observing the focused editor, this
is sent to ``widget`` synchronously because assumed that this notification
causes neither query content events nor composition events.
If ``widget`` wants notifications even while all windows are inactive,
``IMEContentObserver`` doesn't end observing the focused editor. I.e., in this
case, ``NOTIFY_IME_OF_FOCUS`` and ``NOTIFY_IME_OF_BLUR`` are not sent to
``widget`` when a window which has a composition is being activated or
inactivated.
When ``widget`` wants notifications during inactive, ``widget`` includes
``NOTIFY_DURING_DEACTIVE`` to the result of
``nsIWidget::GetIMEUpdatePreference()``.
If this notification is tried to sent before sending ``NOTIFY_IME_OF_FOCUS``,
all pending notifications and ``NOTIFY_IME_OF_BLUR`` itself are canceled.
NOTIFY_IME_OF_TEXT_CHANGE
-------------------------
When text of focused editor is changed, this is sent to ``widget`` with a range
of the change. But this is sent only when result of
``nsIWidget::GetIMEUpdatePreference()`` includes ``NOTIFY_TEXT_CHANGE``.
If two or more text changes occurred after previous
``NOTIFY_IME_OF_TEXT_CHANGE`` or ``NOTIFY_IME_OF_FOCUS``, the ranges of all
changes are merged. E.g., if first change is from ``1`` to ``5`` and second
change is from ``5`` to ``10``, the notified range is from ``1`` to ``10``.
If all merged text changes were caused by composition,
``IMENotification::mTextChangeData::mCausedOnlyByComposition`` is set to true.
This is useful if native IME handler wants to ignore all text changes which are
expected by native IME.
If at least one text change of the merged text changes was caused by current
composition,
``IMENotification::mTextChangeData::mIncludingChangesDuringComposition`` is set
to true. This is useful if native IME handler wants to ignore delayed text
change notifications.
If at least one text change of the merged text changes was caused when there
was no composition,
``IMENotification::mTextChangeData::mIncludingChangesWithoutComposition`` is
set to true.
NOTIFY_IME_OF_SELECTION_CHANGE
------------------------------
When selection (or caret position) is changed in focused editor, widget is
notified of this.
If the last selection change was occurred by a composition event event
handling, ``IMENotification::mSelectionChangeData::mCausedByComposition`` is
set to true. This is useful if native IME handler wants to ignore the last
selection change which is expected by native IME.
If the last selection change was occurred by an ``eSetSelection`` event,
``IMENotification::mSelectionChangeData::mCausedBySelectionEvent`` is set to
true. This is useful if native IME handler wants to ignore the last selection
change which was requested by native IME.
If the last selection is occurred during a composition,
``IMENotification::mSelectionChangeData::mOccurredDuringComposition`` is set to
true. This is useful if native IME handler wants to ignore the last selection
change which occurred by web application's ``compositionstart`` or
``compositionupdate`` event handler before inserting composition string.
NOTIFY_IME_OF_POSITION_CHANGE
-----------------------------
When reflow or scroll occurs in the document, this is sent to widget, but this
is sent only when result of ``nsIWidget::GetIMEUpdatePreference()`` includes
``NOTIFY_POSITION_CHANGE``.
This might be useful to update a candidate window position or something.
NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
---------------------------------------
After ``TextComposition`` handles ``eCompositionStart``,
``eCompositionChange``, ``eComposiitionCommit`` or ``eCompositionCommitAsIs``,
this notification is sent to widget. This might be useful to update a candidate
window position or something.
NOTIFY_IME_OF_MOUSE_BUTTON_EVENT
--------------------------------
When a ``mousedown`` event or a ``mouseup`` event is fired on a character in a
focused editor, this is sent to widget. But this is sent only when result of
``nsIWidget::GetIMEUpdatePreference()`` includes
``NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR``. This is sent with various information.
See ``IMENotification::mMouseButtonEventData`` in
`IMEData.h <https://searchfox.org/mozilla-central/source/widget/IMEData.h>`__
for the detail.
If native IME supports mouse button event handling, ``widget`` should notify
IME of mouse button events with this. If IME consumes an event, ``widget``
should return ``NS_SUCCESS_EVENT_CONSUMED`` from ``nsIWidget::NotifyIME()``.
Then, ``EditorBase`` doesn't handle the mouse event.
Note that if a ``mousedown`` event or a ``mouseup`` event is consumed by a web
application (before a focused editor handles it), this notification is not sent
to ``widget``. This means that web applications can handle mouse button events
before IME.
Requests to IME
===============
XP part of Gecko can request IME to commit or cancel composition. This must be
requested via ``IMEStateManager::NotifyIME()``. Then, ``IMEStateManager`` looks
for a proper ``TextComposition`` instance. If it's found,
``TextComposition::RequestToCommit()`` for calling ``nsIWidget::NotifyIME()``
and handles some extra jobs.
widget should call the proper native API if it's available. Even if commit or
canceling composition does not occur synchronously, widget doesn't need to
emulate it since ``TextComposition`` will emulate it automatically. In other
words, widget should only request to commit or cancel composition to IME.
REQUEST_TO_COMMIT_COMPOSITION
-----------------------------
A request to commit current composition to IME. See also following
"`Forcibly committing composition`_" section for additional information.
REQUEST_TO_CANCEL_COMPOSITION
-----------------------------
A request to cancel current composition to IME. In other words, a request to
commit current composition with an empty string.
Forcibly committing composition
===============================
When ``TextComposition::RequestToCommit()`` calls ``nsIWidget::NotifyIME()``,
it guarantees synchronous commit or canceling composition.
In order to put it into practice, we need to handle the following four
scenarios:
The composition is committed with non-empty string synchronously
----------------------------------------------------------------
This is the most usual case. In this case, ``TextComposition`` handles
``WidgetCompositionEvent`` instances during a request normally. However, in a
remote process in e10s mode, this case never occurs since requests to native
IME is handled asynchronously.
The composition is not committed synchronously but later
--------------------------------------------------------
This is the only case in a remote process in e10s mode or occurs on Linux even
in non-e10s mode if the native IME is iBus. The callers of
``NotifyIME(REQUEST_TO_COMMIT_COMPOSITION)`` may expect that composition string
is committed immediately for their next job. For such a case,
``TextComposition::RequestToCommit()`` synthesizes DOM composition events and a
DOM text event for emulating to commit composition synchronously. Additionally,
``TextComposition`` ignores committing events which are dispatched by widget
when the widget receives native IME events.
In this case, using the last composition string as commit string.
However, if the last composition string is only an ideographic space (fullwidth
space), the composition string may be a placeholder of some old Chinese IME on
Windows.
.. image:: ChangJie.png
:alt: aScreenshot of ChangJie (Traditional Chinese IME) which puts an
ideographic space into composition string for placeholder
In this case, although, we should not commit the placeholder character because
it's not a character which the user wanted to input but we commit it as is. The
reason is, inputting an ideographic space causes a composition. Therefore, we
cannot distinguish if committing composition is unexpected. If the user uses
such old Chinese IME, ``"intl.ime.remove_placeholder_character_at_commit"``
pref may be useful but we don't support them anymore in default settings
(except if somebody will find a good way to fix this issue).
The composition is committed synchronously but with empty string
----------------------------------------------------------------
This case may occur on Linux or with some IME on other platforms. If a web
application implements autocomplete, committing with different strings
especially an empty string it might cause confusion.
In this case, TextComposition overwrites the commit string of
``eCompositionChange`` event dispatched by widget. However, if the last
composition string is only an ideographic space, it shouldn't be committed. See
the previous case.
Note that this case doesn't work as expected when composition is in a remote
process in e10s mode.
The composition is not committed
--------------------------------
On Linux, there is no API to request commit or canceling composition forcibly.
Instead, Gecko uses ``gtk_im_context_reset()`` API for this purpose because
most IME cancel composition with it. But there are some IMEs which do nothing
when Gecko calls it.
If this occurs, Gecko should restart composition with a DOM
``compositionstart`` event , a DOM ``compositionupdate`` event and a DOM
``text`` event at caret position.
.. note::
This issue hasn't been supported yet.
IME state management
====================
IME is a text input system. It means that except when a user wants to input
some text, IME shouldn't be available. For example, pressing the space key to
attempt scrolling a page may be consumed and prevented by IME. Additionally,
password editors need to request special behavior with IME.
For solving this issue, Gecko sets the proper IME state at DOM focus change.
First, when a DOM node gets focus, nsFocusManager notifies ``IMEStateManager``
of the new focused node (calls ``IMEStateManager::OnChangeFocus()``).
``IMEStateManager`` asks desired IME state by calling
``nsIContent::GetDesiredIMEState()`` of the node. If the node owns
``TextEditor`` instance, it asks for the desired IME state from the editor and
returns the result.
Next, ``IMEStateManager`` initializes ``InputContext`` (defined in
`IMEData.h <https://searchfox.org/mozilla-central/source/widget/IMEData.h>`__)
with the desired IME state and node information. Then, it calls
``nsIWidget::SetInputContext()`` with the ``InputContext``.
Finally, widget stores the InputContext and enables or disables IME if the
platform has such an API.
InputContext
------------
InputContext is a struct. Its ``mIMEState``, ``mHTMLInputType``,
``mHTMLInputInputMode`` and ``mActionHint`` are set at
``nsIWidget::SetInputContext()`` called.
mIMEState
^^^^^^^^^
IME state has two abilities. One is enabled state:
ENABLED
"""""""
This means IME is fully available. E.g., when an editable element such as
``<input type="text">``, ``<textarea>`` or ``<foo contenteditable>`` has focus.
DISABLED
""""""""
This means IME is not available. E.g., when a non-editable element has focus or
no element has focus, the desired IME state is ``DISABLED``.
PASSWORD
""""""""
This means IME state should be the same as the state when a native password
field has focus. This state is set only when
``<input type="password"> (ime-mode: auto;)``,
``<input type="text" style="ime-mode: disabled;">`` or
``<textarea style="ime-mode: disabled;">``.
The other is IME open state:
DONT_CHANGE_OPEN_STATE
""""""""""""""""""""""
The open state of IME shouldn't be changed. I.e., Gecko should keep the last
IME open state.
OPEN
""""
Open IME. This is specified only when ime-mode of the new focused element is
``active``.
CLOSE
"""""
Close IME. This is specified only when ime-mode of the new focused element is
``inactive``.
.. note::
E.g., on Linux, applications cannot manage IME open state. On such
platforms, this is ignored.
.. note::
IME open state should be changed only when ``nsIWidget::SetInputContext()``
is called at DOM focus change because changing IME open state while an
editor has focus makes users confused. The reason why
``nsIWidget::SetInputContext()`` is called is stored in
``InputContextAction::mCause``.
How does Gecko disable IME in IMM mode on Windows
"""""""""""""""""""""""""""""""""""""""""""""""""
Every window on Windows is associated an ``IMContext``. When Gecko disables
IME,
`mozilla::widget::IMEHandler <https://searchfox.org/mozilla-central/source/widget/windows/WinIMEHandler.cpp>`__::SetInputContext()
disassociates the context from the window.
How does Gecko disable IME in TSF mode on Windows
"""""""""""""""""""""""""""""""""""""""""""""""""
`mozilla::widget::TSFTextStore <https://searchfox.org/mozilla-central/source/widget/windows/TSFTextStore.cpp>`__
sets focus to a dummy context which disables the keyboard.
How does Gecko disable IME on Mac
"""""""""""""""""""""""""""""""""
`mozilla::widget::TextInputHandler <https://searchfox.org/mozilla-central/source/widget/cocoa/TextInputHandler.mm>`__::HandleKeyDownEvent()
doesn't call focused view's interpretKeyEvents. This prevents native key events
to be passed to IME.
How does Gecko disable IME on GTK
"""""""""""""""""""""""""""""""""
`mozilla::widget::IMContextWrapper <https://searchfox.org/mozilla-central/source/widget/gtk/IMContextWrapper.cpp>`__
sets focus to a dummy context which doesn't have IME composition.
How does Gecko disable IME on Android
"""""""""""""""""""""""""""""""""""""
?
mHTMLInputType
^^^^^^^^^^^^^^
The value is a string representing the focused editor.
``"text"``, ``"password"``, ``"number"``, etc.
When an ``<input>`` element gets focus, the value is the type of the input
element.
``"textarea"``
When a ``<textarea>`` element gets focus, the value is ``"textarea"``.
``""``
When an HTML editor (an element whose ``contenteditable`` attribute is
``true`` or document whose ``designMode`` is ``"on"``) gets focus, the
value is empty. And also, when the other elements get focus.
mHTMLInputMode
^^^^^^^^^^^^^^
The value is ``inputmode`` attribute value of the focused editor.
mActionHint
^^^^^^^^^^^
The value is ``enterkeyhint`` attribute value of the focused editor when
``"dom.forms.enterkeyhint"`` pref is true. This is useful for deciding the
caption for the submit button in virtual keyboard. E.g., the value could be
``"Go"``, ``"Next"`` or ``"Search"``.
Native IME handlers
===================
Following classes handles IME on each platform:
Windows
-------
`mozilla::widget::IMEHandler`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This class manages input method context of each window and makes ``IMMHandler``
or ``TSFTextStore`` work with active IME and focused editor. This class has
only static members, i.e., never created its instance.
__ https://searchfox.org/mozilla-central/source/widget/windows/WinIMEHandler.cpp
`mozilla::widget::IMMHandler`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This class is used when TSF mode is disabled by pref (``"intl.tsf.enabled"``
since 108, formerly named ``"intl.tsf.enable"``) or active IME is for IMM
(i.e., not TIP for TSF).
This class handles ``WM_IME_*`` messages and uses ``Imm*()`` API. This is a
singleton class since Gecko supports only on IM context in a process.
Typically, a process creates windows with default IM context. Therefore, this
design is enough (ideally, an instance should be created per IM context,
though). The singleton instance is created when it becomes necessary.
__ https://searchfox.org/mozilla-central/source/widget/windows/IMMHandler.cpp
`mozilla::widget::TSFTextStore`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This class handles IME events in TSF mode and when TIP (IME implemented with
TSF) is active. This instances are created when an editable element gets focus
and released when it loses focus.
``TSFTextStore`` implements some COM interfaces which is necessary to work with
TIP. And similarly, there is a singleton class, ``TSFStaticSink``, to observe
active TIP changes.
TSF is the most complicated IME API on all platforms, therefore, design of this
class is also very complicated.
FIrst, TSF/TIP requests to lock the editor content for querying or modifying
the content or selection. However, web standards don't have such mechanism.
Therefore, when it's requested, ``TSFTextStore`` caches current content and
selection with ``WidgetQueryContentEvent``. Then, it uses the cache to reply to
query requests, and modifies the cache as they requested. At this time,
``TSFTextStore`` saves the requests of modification into the queue called
``PendingAction``. Finally, after unlocking the contents, it flushes the
pending actions with dispatches ``WidgetCompositionEvent``s via
``TextEventDispatcher``.
Then, ``IMEContentObserver`` will notify some changes caused by the dispatched
``WidgetCompositionEvents`` (they are notified synchronously in chrome or
non-e10s mode, but asynchronously from a remote process in e10s mode). At this
time, ``TSFTextStore`` may receive notifications which indicates web
application changes the content differently from cache in ``TSFTextStore``.
However, ``TSFTextStore`` ignores such fact temporarily until the composition
is finished completely. The reason is that, notifying unexpected text or
selection changes to TSF and/or TIP during composition may behave them odd.
When a composition is committed and it receives
``NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED``, ``TSFTextStore`` clears the cache
of contents and notifying TSF of merged text changes and the last selection
change if they are not caused by composition. By this step, TSF and TIP may
sync its internal cache with actual contents.
Note that if new composition is started before
``NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED`` notification, ``TSFTextStore``
handles the a composition with cached contents which may be different from
actual contents. So, e.g., reconversion around caret may not work as unexpected
in such case, but we don't have a good solution for this issue.
On the other hand, ``TSFTextStore`` cannot cache character rectangles since if
there are a lot of characters, caching the rectangles require a lot of CPU cost
(to compute each rect) and memory. Therefore, ``TSFTextStore`` will use
insertion point relative query for them
`bug 1286157 <https://bugzilla.mozilla.org/show_bug.cgi?id=1286157>`__. Then,
it can retrieve expected character's rect even if the cache of ``TSFTextStore``
is different from the actual contents because TIP typically needs caret
position's character rect (for a popup to indicate current input mode or next
word suggestion list) or first character rect of the target clause of current
composition (for a candidate list window of conversion).
__ https://searchfox.org/mozilla-central/source/widget/windows/TSFTextStore.cpp
Mac
---
Both IME and key events are handled in
`TextInputHandler.mm <https://searchfox.org/mozilla-central/source/widget/cocoa/TextInputHandler.mm>`__.
``mozilla::widget::TextInputHandlerBase`` is the most base class.
``mozilla::widget::IMEInputHandler`` inherits ``TextInputHandlerBase`` and
handles IME related events. ``mozilla::widget::TextInputHandler`` inherits
``TextInputHandlerBase`` and implements ``NSTextInput`` protocol of Cocoa. Its
instance is created per
`nsChildView <https://searchfox.org/mozilla-central/source/widget/cocoa/nsChildView.mm>`__
instance.
GTK
---
`mozilla::widget::IMContextWrapper <https://searchfox.org/mozilla-central/source/widget/gtk/IMContextWrapper.cpp>`__
handles IME. The instance is created per top level window.
Android
-------
`org.mozilla.geckoview.GeckoEditable <https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java>`__ handles native IME events and `mozilla::widget::GeckoEditableSupport <https://searchfox.org/mozilla-central/source/widget/android/GeckoEditableSupport.cpp>`__
dispatches ``Widget*Event``.