GObject's dispose() method may be called multiple times. Guard against
trying to disconnect the same signal multiple times by clearing the ID
to avoid warnings when closing the window.
```
(bug:74019): GLib-GObject-WARNING **: 11:15:08.697: ../../../gobject/gsignal.c:2731: instance '0x55e1c3ea0200' has no handler with id '255'
```
This PR takes a step back and changes the GTK IM client window back from FlView to GtkApplicationWindow as it was before #33111. The window was originally changed to FlView to make the code testable by cutting a dependency to gtk_widget_translate_coordinates(). The change was hard to revert because there were several conflicting changes on top. Therefore, this PR introduces an FlTextInputViewDelegate to be able to provide coordinate mapping in a testable way.
Fixes: flutter/flutter#108832
When Orca has "Speak object under mouse" enabled, it tries to get the
text range extents for any object that implements the AtkText interface
and gets a bit confused by our AtkText stub implementation that merely
returns null for `get_text`.
Removing the stub implementation helps to avoid the issue that Orca
would try to call `atk_text_get_range_extents()` with `start_offset=0`
and `end_offset=0`:
> Atk-CRITICAL **: atk_text_get_range_extents: assertion 'start_offset >= 0 && start_offset < end_offset' failed
`gdk_keymap_get_for_display()` does not transfer ownership to the
caller. Destroying the keymap object would cause problems further down
the road when `GtkWindow` gets to clean up and tries to destroy the
same keymap.
* Make FlAccessibleNode derivable
This allows introducing specialized subclasses that implement such a11y
interfaces as AtkEditableText that should not be implemented for the
generic node.
* FlAccessibleNode: add set value, text selection & perform action
Prepare API required for the upcoming AtkEditableText implementation.
* FlViewAccessible: postpone child node creation
This changes the a11y node creation flow so that (unknown) child nodes
are not pre-created when their parent is updated but "pending" child
node IDs are collected for later. Parent-child relationships are
established at the end of update batches when all nodes have been
created.
* Add FlAccessibleTextField that implements AtkEditableText
Solves: flutter/flutter#103191
* FlEngine: add fl_engine_update_accessibility_features()
* FlSettings: add API for high-contrast & animations
* FlEngine: allow passing mock messenger at construction time
* FlSettingsPlugin: report accessibility features
Merges most (but not all) of the impeller .clang-tidy rules into the
main .clang-tidy config. Merges:
readability-identifier-naming.PrivateMemberSuffix (_)
readability-identifier-naming.EnumConstantPrefix (k)
modernize-use-default-member-init.UseAssignment
Does not merge:
readability-identifier-naming.PublicMethodCase (CamelCase)
readability-identifier-naming.PrivateMethodCase (CamelCase)
These last two are not merged due to the non-trivial number of existing
field accessors that use field_name() methods to directly return
field_name_. While these are permitted by the C++ style guide, we may
want to move to a single, simple rule and name everything in CamelCase.
These can be enabled in a followup patch.
No new tests added, since this change is style-only.
Let MockBinaryMessenger internally manage the FlBinaryMessenger wrapper
instance and provide it via operator FlBinaryMessenger*() to avoid
having to create two objects in every test. The same technique was used
for MockSettings in #32618.
This PR is a continuation of #33111 and includes necessary changes to test the `TextInputType.setClient()` platform channel call. Testing the actual text input flow requires a mock IM context that will follow in a separate PR.
* [Linux] pass GdkWindow instead of FlView to FlTextInputPlugin
* [Linux] add MockBinaryMessenger::ReceiveMessage()
* [Linux] add test for TextInputType.setClient
* Drop the offscreen window
GdkOffscreenWindow requires display & screen which is a problem in
headless mode. Luckily, we don't really need a window instance in tests
because its only passed as a client window to the IM context which will
be mocked out when we get to tests that depend on it.
GTK IM operates in [coordinates relative to its client window](https://docs.gtk.org/gtk3/method.IMContext.set_cursor_location.html#:~:text=The%20location%20is%20relative%20to%20the%20client%C2%A0window.). Let GTK
IM use the native window that backs FlView, which is the same native
window that is used for key event processing - see GDK_KEY_XXX_MASK in
fl_view_realize().
This is an important step towards testable FlTextInputPlugin because it
eliminates the depency to GtkWidget's coordinate translation API and
that way paves the road for replacing FlTextInputPlugin's FlView
instance with a plain GdkWindow. In tests, that window can be an
instance of GdkOffscreenWindow.
There are no tests included yet but after a few more refactoring steps
it will be possible to execute tests like this: [fl_text_input_plugin_test.cc](f2e0d91b59/shell/platform/linux/fl_text_input_plugin_test.cc).
The other refactoring steps are visible at [jpnurmi/engine/commits/text-input-plugin](https://github.com/jpnurmi/engine/commits/text-input-plugin).
Fixes an issue with CJK IMEs wherein a text input state update may be
sent to the framework that misleads the framework into assuming that IME
composing has ended.
As an example, when inputting Korean text, characters are built up keystroke by
keystroke until the point that either:
* the user presses space/enter to terminate composing and commit the
character, or;
* the user presses a key such that the character currently being
composed cannot be modified further, and the IME determines that the
user has begun composing the next character.
The following is an example sequence of events for the latter case:
1. User presses ㅂ. Begin compose event followed by change event
received with ㅂ. Embedder sends state update to framework.
2. User presses ㅏ. im_preedit_changed_cb with 바. Embedder sends state
update to framework.
3. User presses ㄴ. im_preedit_changed_cb with 반. Embedder sends state
update to framework.
4. User presses ㅏ. At this point, the current character being composed
(반) cannot be modified in a meaningful way, and the IME determines
that the user is typing 바 followed by 나. im_commit_cb received with
바, immediately followed by im_preedit_changed event with 나.
In step 4, we previously sent two events to the framework, one
immediately after the other:
* im_commit_cb triggers the text input model to commit the current
composing region to the string under edit. This causes the composing
region to collapse to an empty range.
* im_preedit_change_cb triggers the text input model to insert the new
composing character and set the composing region to that character.
Conceptually, this is an atomic operation. The fourth keystroke causes
the 반 character to be broken into two (바 and ㄴ) and the latter to be
modified to 나. From the user's point of view, as well as from the IME's
point of view, the user has NOT stopped composing, and the composing
region has simply moved on to the next character.
Flutter has no concept of whether the user is composing or not other
that whether a non-empty composing region exists. As such, sending a
state update after the commit event misleads the framework into
believing that composing has ended. This triggers a serious bug:
Text fields with input formatters applied do not perform input
formatting updates while composing is active; instead they wait until
composing has ended to apply any formatting. The previous behaviour
would thus trigger input formatters to be applied each time the user
input caused a new character to be input. This has the add-on negative
effect that once formatting has been applied, it sends an update back to
the embedder so that the native OS text input state can be updated.
However, since the commit event is immediately followed by a
preedit change, the state has changed in the meantime, and the embedder
is left processing an update (the intermediate state sent after the
commit) which is now out of date (i.e. missing the new state from the
change event).
The source of this bug is as follows:
* Commit event for a character/compose region is sent from the engine.
The engine TextInputModel still models its `composing` field as true.
An update is sent to the framework with the committed text and an
empty composing range such as (1, 1). Note that the engine previously
only sends a range of (-1, -1) when composing has ended, NOT just when
it has an empty composing region.
* Framework receives commit event and updates the text to match. The
framework does not model the system composing state; instead its
understanding of whether the user is composing or not is entirely
predicated on whether the composing region is empty or not. If it is,
it triggers input formatters, which in this case have no effect on the
text/selection. However, the framework consistently models empty
compose regions as (-1, -1) and resets the text editing value as such.
Because the framework triggered a change to the TextEditingValue, it
dutifully sends the update back to the engine.
* In the meantime, in parallel with the above step, the engine starts
processing the change event immediately following the commit, and
updates the text and composing region with the next character. This
change is promptly stomped on by the incoming framework update.
To avoid this, we have the engine consistently send empty compose
regions as (-1, -1) to the framework. After the input formatter is
applied on commit, the compose region is still (-1, -1) and there are
therefore no diffs, and the framework will not send an update back to
the engine and stomp on any new state on the engine side.
Longer-term, we really should add some form of versioning information to
the text edit protocol so as to detect and resolve conflicts rather than
relying entirely on not creating races in the first place.
This bug was revealed by flutter/flutter#90211
which applies an input formatter to single-line text fields in order to
suppress newlines.
Issue: flutter/flutter#97174