1 /* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*-
2 * vim: set sw=2 ts=4 expandtab:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "GeckoEditableSupport.h"
9 #include "AndroidRect.h"
11 #include "PuppetWidget.h"
12 #include "nsIContent.h"
14 #include "mozilla/dom/ContentChild.h"
15 #include "mozilla/IMEStateManager.h"
16 #include "mozilla/java/GeckoEditableChildWrappers.h"
17 #include "mozilla/java/GeckoServiceChildProcessWrappers.h"
18 #include "mozilla/Logging.h"
19 #include "mozilla/MiscEvents.h"
20 #include "mozilla/Preferences.h"
21 #include "mozilla/StaticPrefs_intl.h"
22 #include "mozilla/TextComposition.h"
23 #include "mozilla/TextEventDispatcherListener.h"
24 #include "mozilla/TextEvents.h"
25 #include "mozilla/ToString.h"
26 #include "mozilla/dom/BrowserChild.h"
27 #include "mozilla/widget/GeckoViewSupport.h"
29 #include <android/api-level.h>
30 #include <android/input.h>
31 #include <android/log.h>
34 static mozilla::LazyLogModule
sGeckoEditableSupportLog("GeckoEditableSupport");
35 # define ALOGIME(...) \
36 MOZ_LOG(sGeckoEditableSupportLog, LogLevel::Debug, (__VA_ARGS__))
38 # define ALOGIME(args...) \
43 static uint32_t ConvertAndroidKeyCodeToDOMKeyCode(int32_t androidKeyCode
) {
44 // Special-case alphanumeric keycodes because they are most common.
45 if (androidKeyCode
>= AKEYCODE_A
&& androidKeyCode
<= AKEYCODE_Z
) {
46 return androidKeyCode
- AKEYCODE_A
+ NS_VK_A
;
49 if (androidKeyCode
>= AKEYCODE_0
&& androidKeyCode
<= AKEYCODE_9
) {
50 return androidKeyCode
- AKEYCODE_0
+ NS_VK_0
;
53 switch (androidKeyCode
) {
54 // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3)
57 // KEYCODE_CALL (5) ... KEYCODE_POUND (18)
58 case AKEYCODE_DPAD_UP
:
60 case AKEYCODE_DPAD_DOWN
:
62 case AKEYCODE_DPAD_LEFT
:
64 case AKEYCODE_DPAD_RIGHT
:
66 case AKEYCODE_DPAD_CENTER
:
68 case AKEYCODE_VOLUME_UP
:
69 return NS_VK_VOLUME_UP
;
70 case AKEYCODE_VOLUME_DOWN
:
71 return NS_VK_VOLUME_DOWN
;
72 // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54)
77 case AKEYCODE_ALT_LEFT
:
79 case AKEYCODE_ALT_RIGHT
:
81 case AKEYCODE_SHIFT_LEFT
:
83 case AKEYCODE_SHIFT_RIGHT
:
89 // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65)
93 return NS_VK_BACK
; // Backspace
95 return NS_VK_BACK_QUOTE
;
99 case AKEYCODE_LEFT_BRACKET
:
100 return NS_VK_OPEN_BRACKET
;
101 case AKEYCODE_RIGHT_BRACKET
:
102 return NS_VK_CLOSE_BRACKET
;
103 case AKEYCODE_BACKSLASH
:
104 return NS_VK_BACK_SLASH
;
105 case AKEYCODE_SEMICOLON
:
106 return NS_VK_SEMICOLON
;
107 // KEYCODE_APOSTROPHE (75)
110 // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90)
112 return NS_VK_VOLUME_MUTE
;
113 case AKEYCODE_PAGE_UP
:
114 return NS_VK_PAGE_UP
;
115 case AKEYCODE_PAGE_DOWN
:
116 return NS_VK_PAGE_DOWN
;
117 // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110)
118 case AKEYCODE_ESCAPE
:
120 case AKEYCODE_FORWARD_DEL
:
122 case AKEYCODE_CTRL_LEFT
:
123 return NS_VK_CONTROL
;
124 case AKEYCODE_CTRL_RIGHT
:
125 return NS_VK_CONTROL
;
126 case AKEYCODE_CAPS_LOCK
:
127 return NS_VK_CAPS_LOCK
;
128 case AKEYCODE_SCROLL_LOCK
:
129 return NS_VK_SCROLL_LOCK
;
130 // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119)
132 return NS_VK_PRINTSCREEN
;
135 case AKEYCODE_MOVE_HOME
:
137 case AKEYCODE_MOVE_END
:
139 case AKEYCODE_INSERT
:
141 // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130)
166 case AKEYCODE_NUM_LOCK
:
167 return NS_VK_NUM_LOCK
;
168 case AKEYCODE_NUMPAD_0
:
169 return NS_VK_NUMPAD0
;
170 case AKEYCODE_NUMPAD_1
:
171 return NS_VK_NUMPAD1
;
172 case AKEYCODE_NUMPAD_2
:
173 return NS_VK_NUMPAD2
;
174 case AKEYCODE_NUMPAD_3
:
175 return NS_VK_NUMPAD3
;
176 case AKEYCODE_NUMPAD_4
:
177 return NS_VK_NUMPAD4
;
178 case AKEYCODE_NUMPAD_5
:
179 return NS_VK_NUMPAD5
;
180 case AKEYCODE_NUMPAD_6
:
181 return NS_VK_NUMPAD6
;
182 case AKEYCODE_NUMPAD_7
:
183 return NS_VK_NUMPAD7
;
184 case AKEYCODE_NUMPAD_8
:
185 return NS_VK_NUMPAD8
;
186 case AKEYCODE_NUMPAD_9
:
187 return NS_VK_NUMPAD9
;
188 case AKEYCODE_NUMPAD_DIVIDE
:
190 case AKEYCODE_NUMPAD_MULTIPLY
:
191 return NS_VK_MULTIPLY
;
192 case AKEYCODE_NUMPAD_SUBTRACT
:
193 return NS_VK_SUBTRACT
;
194 case AKEYCODE_NUMPAD_ADD
:
196 case AKEYCODE_NUMPAD_DOT
:
197 return NS_VK_DECIMAL
;
198 case AKEYCODE_NUMPAD_COMMA
:
199 return NS_VK_SEPARATOR
;
200 case AKEYCODE_NUMPAD_ENTER
:
202 case AKEYCODE_NUMPAD_EQUALS
:
204 // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210)
206 // Needs to confirm the behavior. If the key switches the open state
207 // of Japanese IME (or switches input character between Hiragana and
208 // Roman numeric characters), then, it might be better to use
209 // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows.
210 case AKEYCODE_ZENKAKU_HANKAKU
:
214 case AKEYCODE_MUHENKAN
:
215 return NS_VK_NONCONVERT
;
216 case AKEYCODE_HENKAN
:
217 return NS_VK_CONVERT
;
218 case AKEYCODE_KATAKANA_HIRAGANA
:
221 return NS_VK_BACK_SLASH
; // Same as other platforms.
223 return NS_VK_BACK_SLASH
; // Same as other platforms.
226 case AKEYCODE_ASSIST
:
229 // the A key is the action key for gamepad devices.
230 case AKEYCODE_BUTTON_A
:
235 "ConvertAndroidKeyCodeToDOMKeyCode: "
236 "No DOM keycode for Android keycode %d",
237 int(androidKeyCode
));
242 static KeyNameIndex
ConvertAndroidKeyCodeToKeyNameIndex(
243 int32_t keyCode
, int32_t action
, int32_t domPrintableKeyValue
) {
244 // Special-case alphanumeric keycodes because they are most common.
245 if (keyCode
>= AKEYCODE_A
&& keyCode
<= AKEYCODE_Z
) {
246 return KEY_NAME_INDEX_USE_STRING
;
249 if (keyCode
>= AKEYCODE_0
&& keyCode
<= AKEYCODE_9
) {
250 return KEY_NAME_INDEX_USE_STRING
;
254 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
256 return aKeyNameIndex;
258 #include "NativeKeyToDOMKeyName.h"
260 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
262 // KEYCODE_0 (7) ... KEYCODE_9 (16)
263 case AKEYCODE_STAR
: // '*' key
264 case AKEYCODE_POUND
: // '#' key
266 // KEYCODE_A (29) ... KEYCODE_Z (54)
268 case AKEYCODE_COMMA
: // ',' key
269 case AKEYCODE_PERIOD
: // '.' key
271 case AKEYCODE_GRAVE
: // '`' key
272 case AKEYCODE_MINUS
: // '-' key
273 case AKEYCODE_EQUALS
: // '=' key
274 case AKEYCODE_LEFT_BRACKET
: // '[' key
275 case AKEYCODE_RIGHT_BRACKET
: // ']' key
276 case AKEYCODE_BACKSLASH
: // '\' key
277 case AKEYCODE_SEMICOLON
: // ';' key
278 case AKEYCODE_APOSTROPHE
: // ''' key
279 case AKEYCODE_SLASH
: // '/' key
280 case AKEYCODE_AT
: // '@' key
281 case AKEYCODE_PLUS
: // '+' key
283 case AKEYCODE_NUMPAD_0
:
284 case AKEYCODE_NUMPAD_1
:
285 case AKEYCODE_NUMPAD_2
:
286 case AKEYCODE_NUMPAD_3
:
287 case AKEYCODE_NUMPAD_4
:
288 case AKEYCODE_NUMPAD_5
:
289 case AKEYCODE_NUMPAD_6
:
290 case AKEYCODE_NUMPAD_7
:
291 case AKEYCODE_NUMPAD_8
:
292 case AKEYCODE_NUMPAD_9
:
293 case AKEYCODE_NUMPAD_DIVIDE
:
294 case AKEYCODE_NUMPAD_MULTIPLY
:
295 case AKEYCODE_NUMPAD_SUBTRACT
:
296 case AKEYCODE_NUMPAD_ADD
:
297 case AKEYCODE_NUMPAD_DOT
:
298 case AKEYCODE_NUMPAD_COMMA
:
299 case AKEYCODE_NUMPAD_EQUALS
:
300 case AKEYCODE_NUMPAD_LEFT_PAREN
:
301 case AKEYCODE_NUMPAD_RIGHT_PAREN
:
303 case AKEYCODE_YEN
: // yen sign key
304 case AKEYCODE_RO
: // Japanese Ro key
305 return KEY_NAME_INDEX_USE_STRING
;
307 case AKEYCODE_NUM
: // XXX Not sure
308 case AKEYCODE_PICTSYMBOLS
:
310 case AKEYCODE_BUTTON_A
:
311 case AKEYCODE_BUTTON_B
:
312 case AKEYCODE_BUTTON_C
:
313 case AKEYCODE_BUTTON_X
:
314 case AKEYCODE_BUTTON_Y
:
315 case AKEYCODE_BUTTON_Z
:
316 case AKEYCODE_BUTTON_L1
:
317 case AKEYCODE_BUTTON_R1
:
318 case AKEYCODE_BUTTON_L2
:
319 case AKEYCODE_BUTTON_R2
:
320 case AKEYCODE_BUTTON_THUMBL
:
321 case AKEYCODE_BUTTON_THUMBR
:
322 case AKEYCODE_BUTTON_START
:
323 case AKEYCODE_BUTTON_SELECT
:
324 case AKEYCODE_BUTTON_MODE
:
326 case AKEYCODE_MEDIA_CLOSE
:
328 case AKEYCODE_BUTTON_1
:
329 case AKEYCODE_BUTTON_2
:
330 case AKEYCODE_BUTTON_3
:
331 case AKEYCODE_BUTTON_4
:
332 case AKEYCODE_BUTTON_5
:
333 case AKEYCODE_BUTTON_6
:
334 case AKEYCODE_BUTTON_7
:
335 case AKEYCODE_BUTTON_8
:
336 case AKEYCODE_BUTTON_9
:
337 case AKEYCODE_BUTTON_10
:
338 case AKEYCODE_BUTTON_11
:
339 case AKEYCODE_BUTTON_12
:
340 case AKEYCODE_BUTTON_13
:
341 case AKEYCODE_BUTTON_14
:
342 case AKEYCODE_BUTTON_15
:
343 case AKEYCODE_BUTTON_16
:
344 return KEY_NAME_INDEX_Unidentified
;
346 case AKEYCODE_UNKNOWN
:
347 MOZ_ASSERT(action
!= AKEY_EVENT_ACTION_MULTIPLE
,
348 "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!");
349 // It's actually an unknown key if the action isn't ACTION_MULTIPLE.
350 // However, it might cause text input. So, let's check the value.
351 return domPrintableKeyValue
? KEY_NAME_INDEX_USE_STRING
352 : KEY_NAME_INDEX_Unidentified
;
356 "ConvertAndroidKeyCodeToKeyNameIndex: "
357 "No DOM key name index for Android keycode %d",
359 return KEY_NAME_INDEX_Unidentified
;
363 static CodeNameIndex
ConvertAndroidScanCodeToCodeNameIndex(int32_t scanCode
) {
365 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
367 return aCodeNameIndex;
369 #include "NativeKeyToDOMCodeName.h"
371 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
374 return CODE_NAME_INDEX_UNKNOWN
;
378 static void InitKeyEvent(WidgetKeyboardEvent
& aEvent
, int32_t aAction
,
379 int32_t aKeyCode
, int32_t aScanCode
,
380 int32_t aMetaState
, int64_t aTime
,
381 int32_t aDomPrintableKeyValue
, int32_t aRepeatCount
,
383 const uint32_t domKeyCode
= ConvertAndroidKeyCodeToDOMKeyCode(aKeyCode
);
385 aEvent
.mModifiers
= nsWindow::GetModifiers(aMetaState
);
386 aEvent
.mKeyCode
= domKeyCode
;
389 (aEvent
.mMessage
== eKeyDown
|| aEvent
.mMessage
== eKeyPress
) &&
390 ((aFlags
& java::sdk::KeyEvent::FLAG_LONG_PRESS
) || aRepeatCount
);
392 aEvent
.mKeyNameIndex
= ConvertAndroidKeyCodeToKeyNameIndex(
393 aKeyCode
, aAction
, aDomPrintableKeyValue
);
394 aEvent
.mCodeNameIndex
= ConvertAndroidScanCodeToCodeNameIndex(aScanCode
);
396 if (aEvent
.mKeyNameIndex
== KEY_NAME_INDEX_USE_STRING
&&
397 aDomPrintableKeyValue
) {
398 aEvent
.mKeyValue
= char16_t(aDomPrintableKeyValue
);
402 WidgetKeyboardEvent::ComputeLocationFromCodeValue(aEvent
.mCodeNameIndex
);
403 aEvent
.mTime
= aTime
;
404 aEvent
.mTimeStamp
= nsWindow::GetEventTimeStamp(aTime
);
407 static nscolor
ConvertAndroidColor(uint32_t aArgb
) {
408 return NS_RGBA((aArgb
& 0x00ff0000) >> 16, (aArgb
& 0x0000ff00) >> 8,
409 (aArgb
& 0x000000ff), (aArgb
& 0xff000000) >> 24);
412 static jni::ObjectArray::LocalRef
ConvertRectArrayToJavaRectFArray(
413 const nsTArray
<LayoutDeviceIntRect
>& aRects
) {
414 const size_t length
= aRects
.Length();
415 auto rects
= jni::ObjectArray::New
<java::sdk::RectF
>(length
);
417 for (size_t i
= 0; i
< length
; i
++) {
418 const LayoutDeviceIntRect
& tmp
= aRects
[i
];
420 auto rect
= java::sdk::RectF::New(tmp
.x
, tmp
.y
, tmp
.XMost(), tmp
.YMost());
421 rects
->SetElement(i
, rect
);
429 NS_IMPL_ISUPPORTS(GeckoEditableSupport
, TextEventDispatcherListener
,
430 nsISupportsWeakReference
)
432 // This is the blocker helper class whether disposing GeckoEditableChild now.
433 // During JNI call from GeckoEditableChild, we shouldn't dispose it.
434 class MOZ_RAII AutoGeckoEditableBlocker final
{
436 explicit AutoGeckoEditableBlocker(GeckoEditableSupport
* aGeckoEditableSupport
)
437 : mGeckoEditable(aGeckoEditableSupport
) {
438 mGeckoEditable
->AddBlocker();
440 ~AutoGeckoEditableBlocker() { mGeckoEditable
->ReleaseBlocker(); }
443 RefPtr
<GeckoEditableSupport
> mGeckoEditable
;
446 RefPtr
<TextComposition
> GeckoEditableSupport::GetComposition() const {
447 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
448 return widget
? IMEStateManager::GetTextCompositionFor(widget
) : nullptr;
451 bool GeckoEditableSupport::RemoveComposition(RemoveCompositionFlag aFlag
) {
452 if (!mDispatcher
|| !mDispatcher
->IsComposing()) {
456 nsEventStatus status
= nsEventStatus_eIgnore
;
458 NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher
), false);
459 mDispatcher
->CommitComposition(
460 status
, aFlag
== CANCEL_IME_COMPOSITION
? &EmptyString() : nullptr);
464 void GeckoEditableSupport::OnKeyEvent(int32_t aAction
, int32_t aKeyCode
,
465 int32_t aScanCode
, int32_t aMetaState
,
466 int32_t aKeyPressMetaState
, int64_t aTime
,
467 int32_t aDomPrintableKeyValue
,
468 int32_t aRepeatCount
, int32_t aFlags
,
469 bool aIsSynthesizedImeKey
,
470 jni::Object::Param aOriginalEvent
) {
471 AutoGeckoEditableBlocker
blocker(this);
473 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
474 RefPtr
<TextEventDispatcher
> dispatcher
=
475 mDispatcher
? mDispatcher
.get()
476 : widget
? widget
->GetTextEventDispatcher()
478 NS_ENSURE_TRUE_VOID(dispatcher
&& widget
);
480 if (!aIsSynthesizedImeKey
) {
481 if (nsWindow
* window
= GetNsWindow()) {
482 window
->UserActivity();
484 } else if (aIsSynthesizedImeKey
&& mIMEMaskEventsCount
> 0) {
485 // Don't synthesize editor keys when not focused.
490 if (aAction
== java::sdk::KeyEvent::ACTION_DOWN
) {
492 } else if (aAction
== java::sdk::KeyEvent::ACTION_UP
) {
494 } else if (aAction
== java::sdk::KeyEvent::ACTION_MULTIPLE
) {
495 // Keys with multiple action are handled in Java,
496 // and we should never see one here
497 MOZ_CRASH("Cannot handle key with multiple action");
499 NS_WARNING("Unknown key action event");
503 nsEventStatus status
= nsEventStatus_eIgnore
;
504 WidgetKeyboardEvent
event(true, msg
, widget
);
505 InitKeyEvent(event
, aAction
, aKeyCode
, aScanCode
, aMetaState
, aTime
,
506 aDomPrintableKeyValue
, aRepeatCount
, aFlags
);
508 if (nsIWidget::UsePuppetWidgets()) {
509 // Don't use native key bindings.
510 event
.PreventNativeKeyBindings();
513 if (aIsSynthesizedImeKey
) {
514 // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
515 // array until the next IME_REPLACE_TEXT event, at which point
516 // these keys are dispatched in sequence.
517 mIMEKeyEvents
.AppendElement(UniquePtr
<WidgetEvent
>(event
.Duplicate()));
519 NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(dispatcher
));
520 dispatcher
->DispatchKeyboardEvent(msg
, event
, status
);
521 if (widget
->Destroyed() || status
== nsEventStatus_eConsumeNoDefault
) {
522 // Skip default processing.
525 mEditable
->OnDefaultKeyEvent(aOriginalEvent
);
528 // Only send keypress after keydown.
529 if (msg
!= eKeyDown
) {
533 WidgetKeyboardEvent
pressEvent(true, eKeyPress
, widget
);
534 InitKeyEvent(pressEvent
, aAction
, aKeyCode
, aScanCode
, aKeyPressMetaState
,
535 aTime
, aDomPrintableKeyValue
, aRepeatCount
, aFlags
);
537 if (nsIWidget::UsePuppetWidgets()) {
538 // Don't use native key bindings.
539 pressEvent
.PreventNativeKeyBindings();
542 if (aIsSynthesizedImeKey
) {
543 mIMEKeyEvents
.AppendElement(UniquePtr
<WidgetEvent
>(pressEvent
.Duplicate()));
545 dispatcher
->MaybeDispatchKeypressEvents(pressEvent
, status
);
550 * Send dummy key events for pages that are unaware of input events,
551 * to provide web compatibility for pages that depend on key events.
553 void GeckoEditableSupport::SendIMEDummyKeyEvent(nsIWidget
* aWidget
,
555 AutoGeckoEditableBlocker
blocker(this);
557 nsEventStatus status
= nsEventStatus_eIgnore
;
558 MOZ_ASSERT(mDispatcher
);
560 WidgetKeyboardEvent
event(true, msg
, aWidget
);
561 event
.mTime
= PR_Now() / 1000;
562 // TODO: If we can know scan code of the key event which caused replacing
563 // composition string, we should set mCodeNameIndex here. Then,
564 // we should rename this method because it becomes not a "dummy"
566 event
.mKeyCode
= NS_VK_PROCESSKEY
;
567 event
.mKeyNameIndex
= KEY_NAME_INDEX_Process
;
568 // KeyboardEvents marked as "processed by IME" shouldn't cause any edit
569 // actions. So, we should set their native key binding to none before
570 // dispatch to avoid crash on PuppetWidget and avoid running redundant
571 // path to look for native key bindings.
572 event
.PreventNativeKeyBindings();
573 NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(mDispatcher
));
574 mDispatcher
->DispatchKeyboardEvent(msg
, event
, status
);
577 void GeckoEditableSupport::AddIMETextChange(
578 const IMENotification::TextChangeDataBase
& aChange
) {
579 mIMEPendingTextChange
.MergeWith(aChange
);
581 // We may not be in the middle of flushing,
582 // in which case this flag is meaningless.
583 mIMETextChangedDuringFlush
= true;
586 void GeckoEditableSupport::PostFlushIMEChanges() {
587 if (mIMEPendingTextChange
.IsValid() || mIMESelectionChanged
) {
592 RefPtr
<GeckoEditableSupport
> self(this);
594 nsAppShell::PostEvent([this, self
] {
595 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
596 if (widget
&& !widget
->Destroyed()) {
602 void GeckoEditableSupport::FlushIMEChanges(FlushChangesFlag aFlags
) {
603 // Only send change notifications if we are *not* masking events,
604 // i.e. if we have a focused editor,
605 NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount
);
607 if (mIMEDelaySynchronizeReply
&& mIMEActiveCompositionCount
> 0) {
608 // We are still expecting more composition events to be handled. Once
609 // that happens, FlushIMEChanges will be called again.
613 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
614 NS_ENSURE_TRUE_VOID(widget
);
617 TextRecord() : start(-1), oldEnd(-1), newEnd(-1) {}
619 bool IsValid() const { return start
>= 0; }
626 TextRecord textTransaction
;
628 nsEventStatus status
= nsEventStatus_eIgnore
;
629 bool causedOnlyByComposition
= mIMEPendingTextChange
.IsValid() &&
630 mIMEPendingTextChange
.mCausedOnlyByComposition
;
631 mIMETextChangedDuringFlush
= false;
633 auto shouldAbort
= [=](bool aForce
) -> bool {
634 if (!aForce
&& !mIMETextChangedDuringFlush
) {
637 // A query event could have triggered more text changes to come in, as
638 // indicated by our flag. If that happens, try flushing IME changes
640 if (aFlags
== FLUSH_FLAG_NONE
) {
641 FlushIMEChanges(FLUSH_FLAG_RETRY
);
643 // Don't retry if already retrying, to avoid infinite loops.
644 __android_log_print(ANDROID_LOG_WARN
, "GeckoEditableSupport",
645 "Already retrying IME flush");
650 if (mIMEPendingTextChange
.IsValid() &&
651 (mIMEPendingTextChange
.mStartOffset
!=
652 mIMEPendingTextChange
.mRemovedEndOffset
||
653 mIMEPendingTextChange
.mStartOffset
!=
654 mIMEPendingTextChange
.mAddedEndOffset
)) {
655 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
658 if (mIMEPendingTextChange
.mAddedEndOffset
!=
659 mIMEPendingTextChange
.mStartOffset
) {
660 queryTextContentEvent
.InitForQueryTextContent(
661 mIMEPendingTextChange
.mStartOffset
,
662 mIMEPendingTextChange
.mAddedEndOffset
-
663 mIMEPendingTextChange
.mStartOffset
);
664 widget
->DispatchEvent(&queryTextContentEvent
, status
);
666 if (shouldAbort(NS_WARN_IF(queryTextContentEvent
.Failed()))) {
670 textTransaction
.text
= queryTextContentEvent
.mReply
->DataRef();
673 textTransaction
.start
=
674 static_cast<int32_t>(mIMEPendingTextChange
.mStartOffset
);
675 textTransaction
.oldEnd
=
676 static_cast<int32_t>(mIMEPendingTextChange
.mRemovedEndOffset
);
677 textTransaction
.newEnd
=
678 static_cast<int32_t>(mIMEPendingTextChange
.mAddedEndOffset
);
681 int32_t selStart
= -1;
684 if (mIMESelectionChanged
) {
685 if (mCachedSelection
.IsValid()) {
686 selStart
= mCachedSelection
.mStartOffset
;
687 selEnd
= mCachedSelection
.mEndOffset
;
689 // XXX Unfortunately we don't know current selection via selection
690 // change notification.
691 // eQuerySelectedText might be newer data than text change data.
692 // It means that GeckoEditableChild.onSelectionChange may throw
693 // IllegalArgumentException since we don't merge with newer text
695 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
697 widget
->DispatchEvent(&querySelectedTextEvent
, status
);
700 NS_WARN_IF(querySelectedTextEvent
.DidNotFindSelection()))) {
705 static_cast<int32_t>(querySelectedTextEvent
.mReply
->AnchorOffset());
707 static_cast<int32_t>(querySelectedTextEvent
.mReply
->FocusOffset());
710 if (aFlags
== FLUSH_FLAG_RECOVER
&& textTransaction
.IsValid()) {
711 // Sometimes we get out-of-bounds selection during recovery.
712 // Limit the offsets so we don't crash.
713 const int32_t end
= textTransaction
.start
+ textTransaction
.text
.Length();
714 selStart
= std::min(selStart
, end
);
715 selEnd
= std::min(selEnd
, end
);
719 JNIEnv
* const env
= jni::GetGeckoThreadEnv();
720 auto flushOnException
= [=]() -> bool {
721 if (!env
->ExceptionCheck()) {
724 if (aFlags
!= FLUSH_FLAG_RECOVER
) {
725 // First time seeing an exception; try flushing text.
726 env
->ExceptionClear();
727 __android_log_print(ANDROID_LOG_WARN
, "GeckoEditableSupport",
728 "Recovering from IME exception");
729 FlushIMEText(FLUSH_FLAG_RECOVER
);
731 // Give up because we've already tried.
732 #ifdef RELEASE_OR_BETA
733 env
->ExceptionClear();
735 MOZ_CATCH_JNI_EXCEPTION(env
);
741 // Commit the text change and selection change transaction.
742 mIMEPendingTextChange
.Clear();
744 if (textTransaction
.IsValid()) {
745 mEditable
->OnTextChange(textTransaction
.text
, textTransaction
.start
,
746 textTransaction
.oldEnd
, textTransaction
.newEnd
);
747 if (flushOnException()) {
752 while (mIMEDelaySynchronizeReply
&& mIMEActiveSynchronizeCount
) {
753 mIMEActiveSynchronizeCount
--;
754 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT
);
756 mIMEDelaySynchronizeReply
= false;
757 mIMEActiveSynchronizeCount
= 0;
758 mIMEActiveCompositionCount
= 0;
760 if (mIMESelectionChanged
) {
761 mIMESelectionChanged
= false;
763 // mCausedOnlyByComposition may be true on committing text.
764 // So even if true, there is no composition.
765 causedOnlyByComposition
&= mDispatcher
->IsComposing();
767 mEditable
->OnSelectionChange(selStart
, selEnd
, causedOnlyByComposition
);
772 void GeckoEditableSupport::FlushIMEText(FlushChangesFlag aFlags
) {
773 NS_WARNING_ASSERTION(
774 !mIMEDelaySynchronizeReply
|| !mIMEActiveCompositionCount
,
775 "Cannot synchronize Java text with Gecko text");
777 // Notify Java of the newly focused content
778 mIMEPendingTextChange
.Clear();
779 mIMESelectionChanged
= true;
781 // Use 'INT32_MAX / 2' here because subsequent text changes might combine
782 // with this text change, and overflow might occur if we just use
784 IMENotification
notification(NOTIFY_IME_OF_TEXT_CHANGE
);
785 notification
.mTextChangeData
.mStartOffset
= 0;
786 notification
.mTextChangeData
.mRemovedEndOffset
= INT32_MAX
/ 2;
787 notification
.mTextChangeData
.mAddedEndOffset
= INT32_MAX
/ 2;
788 NotifyIME(mDispatcher
, notification
);
790 FlushIMEChanges(aFlags
);
793 void GeckoEditableSupport::UpdateCompositionRects() {
794 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
795 RefPtr
<TextComposition
> composition(GetComposition());
796 NS_ENSURE_TRUE_VOID(mDispatcher
&& widget
);
798 jni::ObjectArray::LocalRef rects
;
800 nsEventStatus status
= nsEventStatus_eIgnore
;
801 uint32_t offset
= composition
->NativeOffsetOfStartComposition();
802 WidgetQueryContentEvent
queryTextRectsEvent(true, eQueryTextRectArray
,
804 queryTextRectsEvent
.InitForQueryTextRectArray(
805 offset
, composition
->String().Length());
806 widget
->DispatchEvent(&queryTextRectsEvent
, status
);
807 rects
= ConvertRectArrayToJavaRectFArray(
808 queryTextRectsEvent
.Succeeded()
809 ? queryTextRectsEvent
.mReply
->mRectArray
810 : CopyableTArray
<mozilla::LayoutDeviceIntRect
>());
812 rects
= ConvertRectArrayToJavaRectFArray(
813 CopyableTArray
<mozilla::LayoutDeviceIntRect
>());
816 WidgetQueryContentEvent
queryCaretRectEvent(true, eQueryCaretRect
, widget
);
817 WidgetQueryContentEvent::Options options
;
818 options
.mRelativeToInsertionPoint
= true;
819 queryCaretRectEvent
.InitForQueryCaretRect(0, options
);
821 nsEventStatus status
= nsEventStatus_eIgnore
;
822 widget
->DispatchEvent(&queryCaretRectEvent
, status
);
824 queryCaretRectEvent
.Succeeded()
825 ? java::sdk::RectF::New(queryCaretRectEvent
.mReply
->mRect
.x
,
826 queryCaretRectEvent
.mReply
->mRect
.y
,
827 queryCaretRectEvent
.mReply
->mRect
.XMost(),
828 queryCaretRectEvent
.mReply
->mRect
.YMost())
829 : java::sdk::RectF::New();
831 mEditable
->UpdateCompositionRects(rects
, caretRect
);
834 void GeckoEditableSupport::OnImeSynchronize() {
835 AutoGeckoEditableBlocker
blocker(this);
837 if (mIMEDelaySynchronizeReply
) {
838 // If we are waiting for other events to reply,
839 // queue this reply as well.
840 mIMEActiveSynchronizeCount
++;
843 if (!mIMEMaskEventsCount
) {
846 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT
);
849 void GeckoEditableSupport::OnImeReplaceText(int32_t aStart
, int32_t aEnd
,
850 jni::String::Param aText
) {
851 AutoGeckoEditableBlocker
blocker(this);
853 if (DoReplaceText(aStart
, aEnd
, aText
)) {
854 mIMEDelaySynchronizeReply
= true;
860 bool GeckoEditableSupport::DoReplaceText(int32_t aStart
, int32_t aEnd
,
861 jni::String::Param aText
) {
862 ALOGIME("IME: IME_REPLACE_TEXT: text=\"%s\"",
863 NS_ConvertUTF16toUTF8(aText
->ToString()).get());
865 // Return true if processed and we should reply to the OnImeReplaceText
866 // event later. Return false if _not_ processed and we should reply to the
867 // OnImeReplaceText event now.
869 if (mIMEMaskEventsCount
> 0) {
870 // Not focused; still reply to events, but don't do anything else.
874 if (nsWindow
* window
= GetNsWindow()) {
875 window
->UserActivity();
879 Replace text in Gecko thread from aStart to aEnd with the string text.
881 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
882 NS_ENSURE_TRUE(mDispatcher
&& widget
, false);
883 NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher
), false);
885 RefPtr
<TextComposition
> composition(GetComposition());
886 MOZ_ASSERT(!composition
|| !composition
->IsEditorHandlingEvent());
888 nsString
string(aText
->ToString());
889 const bool composing
= !mIMERanges
->IsEmpty();
890 nsEventStatus status
= nsEventStatus_eIgnore
;
891 bool textChanged
= composing
;
892 // Whether deleting content before setting or committing composition text.
893 bool performDeletion
= false;
894 // Dispatch composition start to set current composition.
895 bool needDispatchCompositionStart
= false;
897 if (!mIMEKeyEvents
.IsEmpty() || !composition
|| !mDispatcher
->IsComposing() ||
898 uint32_t(aStart
) != composition
->NativeOffsetOfStartComposition() ||
899 uint32_t(aEnd
) != composition
->NativeOffsetOfStartComposition() +
900 composition
->String().Length()) {
901 // Only start a new composition if we have key events,
902 // if we don't have an existing composition, or
903 // the replaced text does not match our composition.
904 textChanged
|= RemoveComposition();
908 nsEventStatus status
= nsEventStatus_eIgnore
;
909 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
911 widget
->DispatchEvent(&querySelectedTextEvent
, status
);
912 if (querySelectedTextEvent
.Succeeded()) {
914 "IME: Current selection: %s",
915 ToString(querySelectedTextEvent
.mReply
->mOffsetAndData
).c_str());
920 // If aStart or aEnd is negative value, we use current selection instead
921 // of updating the selection.
922 if (aStart
>= 0 && aEnd
>= 0) {
923 // Use text selection to set target position(s) for
924 // insert, or replace, of text.
925 WidgetSelectionEvent
event(true, eSetSelection
, widget
);
926 event
.mOffset
= uint32_t(aStart
);
927 event
.mLength
= uint32_t(aEnd
- aStart
);
928 event
.mExpandToClusterBoundary
= false;
929 event
.mReason
= nsISelectionListener::IME_REASON
;
930 widget
->DispatchEvent(&event
, status
);
933 if (!mIMEKeyEvents
.IsEmpty()) {
934 bool ignoreNextKeyPress
= false;
935 for (uint32_t i
= 0; i
< mIMEKeyEvents
.Length(); i
++) {
936 const auto event
= mIMEKeyEvents
[i
]->AsKeyboardEvent();
937 // widget for duplicated events is initially nullptr.
938 event
->mWidget
= widget
;
940 status
= nsEventStatus_eIgnore
;
941 if (event
->mMessage
!= eKeyPress
) {
942 mDispatcher
->DispatchKeyboardEvent(event
->mMessage
, *event
, status
);
943 // Skip default processing. It means that next key press shouldn't
945 ignoreNextKeyPress
= event
->mMessage
== eKeyDown
&&
946 status
== nsEventStatus_eConsumeNoDefault
;
948 if (ignoreNextKeyPress
) {
949 // Don't dispatch key press since previous key down is consumed.
950 ignoreNextKeyPress
= false;
953 mDispatcher
->MaybeDispatchKeypressEvents(*event
, status
);
954 if (status
== nsEventStatus_eConsumeNoDefault
) {
958 if (!mDispatcher
|| widget
->Destroyed()) {
959 // Don't wait for any text change event.
964 mIMEKeyEvents
.Clear();
968 if (aStart
!= aEnd
) {
970 // Actually Gecko doesn't start composition, so it is unnecessary to
971 // delete content before setting composition string.
972 needDispatchCompositionStart
= true;
974 // Perform a deletion first.
975 performDeletion
= true;
978 } else if (composition
->String().Equals(string
)) {
979 /* If the new text is the same as the existing composition text,
980 * the NS_COMPOSITION_CHANGE event does not generate a text
981 * change notification. However, the Java side still expects
982 * one, so we manually generate a notification. */
983 IMENotification::TextChangeData
dummyChange(aStart
, aEnd
, aEnd
, false,
985 PostFlushIMEChanges();
986 mIMESelectionChanged
= true;
987 AddIMETextChange(dummyChange
);
992 intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
993 mInputContext
.mMayBeIMEUnaware
) {
994 SendIMEDummyKeyEvent(widget
, eKeyDown
);
995 if (!mDispatcher
|| widget
->Destroyed()) {
1000 if (needDispatchCompositionStart
) {
1001 // StartComposition sets composition string from selected string.
1002 nsEventStatus status
= nsEventStatus_eIgnore
;
1003 mDispatcher
->StartComposition(status
);
1004 if (!mDispatcher
|| widget
->Destroyed()) {
1007 } else if (performDeletion
) {
1008 WidgetContentCommandEvent
event(true, eContentCommandDelete
, widget
);
1009 event
.mTime
= PR_Now() / 1000;
1010 widget
->DispatchEvent(&event
, status
);
1011 if (!mDispatcher
|| widget
->Destroyed()) {
1018 mDispatcher
->SetPendingComposition(string
, mIMERanges
);
1019 mDispatcher
->FlushPendingComposition(status
);
1020 mIMEActiveCompositionCount
++;
1021 // Ensure IME ranges are empty.
1022 mIMERanges
->Clear();
1023 } else if (!string
.IsEmpty() || mDispatcher
->IsComposing()) {
1024 mDispatcher
->CommitComposition(status
, &string
);
1025 mIMEActiveCompositionCount
++;
1028 if (!mDispatcher
|| widget
->Destroyed()) {
1033 intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
1034 mInputContext
.mMayBeIMEUnaware
) {
1035 SendIMEDummyKeyEvent(widget
, eKeyUp
);
1036 // Widget may be destroyed after dispatching the above event.
1041 void GeckoEditableSupport::OnImeAddCompositionRange(
1042 int32_t aStart
, int32_t aEnd
, int32_t aRangeType
, int32_t aRangeStyle
,
1043 int32_t aRangeLineStyle
, bool aRangeBoldLine
, int32_t aRangeForeColor
,
1044 int32_t aRangeBackColor
, int32_t aRangeLineColor
) {
1045 AutoGeckoEditableBlocker
blocker(this);
1047 if (mIMEMaskEventsCount
> 0) {
1053 range
.mStartOffset
= aStart
;
1054 range
.mEndOffset
= aEnd
;
1055 range
.mRangeType
= ToTextRangeType(aRangeType
);
1056 range
.mRangeStyle
.mDefinedStyles
= aRangeStyle
;
1057 range
.mRangeStyle
.mLineStyle
= TextRangeStyle::ToLineStyle(aRangeLineStyle
);
1058 range
.mRangeStyle
.mIsBoldLine
= aRangeBoldLine
;
1059 range
.mRangeStyle
.mForegroundColor
=
1060 ConvertAndroidColor(uint32_t(aRangeForeColor
));
1061 range
.mRangeStyle
.mBackgroundColor
=
1062 ConvertAndroidColor(uint32_t(aRangeBackColor
));
1063 range
.mRangeStyle
.mUnderlineColor
=
1064 ConvertAndroidColor(uint32_t(aRangeLineColor
));
1065 mIMERanges
->AppendElement(range
);
1068 void GeckoEditableSupport::OnImeUpdateComposition(int32_t aStart
, int32_t aEnd
,
1070 AutoGeckoEditableBlocker
blocker(this);
1072 if (DoUpdateComposition(aStart
, aEnd
, aFlags
)) {
1073 mIMEDelaySynchronizeReply
= true;
1077 bool GeckoEditableSupport::DoUpdateComposition(int32_t aStart
, int32_t aEnd
,
1079 if (mIMEMaskEventsCount
> 0) {
1084 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
1085 nsEventStatus status
= nsEventStatus_eIgnore
;
1086 NS_ENSURE_TRUE(mDispatcher
&& widget
, false);
1088 const bool keepCurrent
=
1089 !!(aFlags
& java::GeckoEditableChild::FLAG_KEEP_CURRENT_COMPOSITION
);
1091 // A composition with no ranges means we want to set the selection.
1092 if (mIMERanges
->IsEmpty()) {
1093 if (keepCurrent
&& mDispatcher
->IsComposing()) {
1094 // Don't set selection if we want to keep current composition.
1098 MOZ_ASSERT(aStart
>= 0 && aEnd
>= 0);
1099 const bool compositionChanged
= RemoveComposition();
1101 WidgetSelectionEvent
selEvent(true, eSetSelection
, widget
);
1102 selEvent
.mOffset
= std::min(aStart
, aEnd
);
1103 selEvent
.mLength
= std::max(aStart
, aEnd
) - selEvent
.mOffset
;
1104 selEvent
.mReversed
= aStart
> aEnd
;
1105 selEvent
.mExpandToClusterBoundary
= false;
1106 widget
->DispatchEvent(&selEvent
, status
);
1107 return compositionChanged
;
1111 * Update the composition from aStart to aEnd using information from added
1112 * ranges. This is only used for visual indication and does not affect the
1113 * text content. Only the offsets are specified and not the text content
1114 * to eliminate the possibility of this event altering the text content
1118 RefPtr
<TextComposition
> composition(GetComposition());
1119 MOZ_ASSERT(!composition
|| !composition
->IsEditorHandlingEvent());
1121 if (!composition
|| !mDispatcher
->IsComposing() ||
1122 uint32_t(aStart
) != composition
->NativeOffsetOfStartComposition() ||
1123 uint32_t(aEnd
) != composition
->NativeOffsetOfStartComposition() +
1124 composition
->String().Length()) {
1126 // Don't start a new composition if we want to keep the current one.
1127 mIMERanges
->Clear();
1131 // Only start new composition if we don't have an existing one,
1132 // or if the existing composition doesn't match the new one.
1133 RemoveComposition();
1136 WidgetSelectionEvent
event(true, eSetSelection
, widget
);
1137 event
.mOffset
= uint32_t(aStart
);
1138 event
.mLength
= uint32_t(aEnd
- aStart
);
1139 event
.mExpandToClusterBoundary
= false;
1140 event
.mReason
= nsISelectionListener::IME_REASON
;
1141 widget
->DispatchEvent(&event
, status
);
1145 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
1147 widget
->DispatchEvent(&querySelectedTextEvent
, status
);
1148 MOZ_ASSERT(querySelectedTextEvent
.Succeeded());
1149 if (querySelectedTextEvent
.FoundSelection()) {
1150 string
= querySelectedTextEvent
.mReply
->DataRef();
1154 // If the new composition matches the existing composition,
1155 // reuse the old composition.
1156 string
= composition
->String();
1159 ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%zu, range=%zu",
1160 NS_ConvertUTF16toUTF8(string
).get(), string
.Length(),
1161 mIMERanges
->Length());
1163 if (NS_WARN_IF(NS_FAILED(BeginInputTransaction(mDispatcher
)))) {
1164 mIMERanges
->Clear();
1167 mDispatcher
->SetPendingComposition(string
, mIMERanges
);
1168 mDispatcher
->FlushPendingComposition(status
);
1169 mIMEActiveCompositionCount
++;
1170 mIMERanges
->Clear();
1174 void GeckoEditableSupport::OnImeRequestCursorUpdates(int aRequestMode
) {
1175 AutoGeckoEditableBlocker
blocker(this);
1177 if (aRequestMode
== EditableClient::ONE_SHOT
) {
1178 UpdateCompositionRects();
1182 mIMEMonitorCursor
= (aRequestMode
== EditableClient::START_MONITOR
);
1185 class MOZ_STACK_CLASS AutoSelectionRestore final
{
1187 explicit AutoSelectionRestore(nsIWidget
* widget
,
1188 TextEventDispatcher
* dispatcher
)
1189 : mWidget(widget
), mDispatcher(dispatcher
) {
1191 if (!dispatcher
|| !dispatcher
->IsComposing()) {
1192 mOffset
= UINT32_MAX
;
1193 mLength
= UINT32_MAX
;
1196 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
1198 nsEventStatus status
= nsEventStatus_eIgnore
;
1199 widget
->DispatchEvent(&querySelectedTextEvent
, status
);
1200 if (querySelectedTextEvent
.DidNotFindSelection()) {
1201 mOffset
= UINT32_MAX
;
1202 mLength
= UINT32_MAX
;
1206 mOffset
= querySelectedTextEvent
.mReply
->StartOffset();
1207 mLength
= querySelectedTextEvent
.mReply
->DataLength();
1210 ~AutoSelectionRestore() {
1211 if (mWidget
->Destroyed() || mOffset
== UINT32_MAX
) {
1215 WidgetSelectionEvent
selection(true, eSetSelection
, mWidget
);
1216 selection
.mOffset
= mOffset
;
1217 selection
.mLength
= mLength
;
1218 selection
.mExpandToClusterBoundary
= false;
1219 selection
.mReason
= nsISelectionListener::IME_REASON
;
1220 nsEventStatus status
= nsEventStatus_eIgnore
;
1221 mWidget
->DispatchEvent(&selection
, status
);
1225 nsCOMPtr
<nsIWidget
> mWidget
;
1226 RefPtr
<TextEventDispatcher
> mDispatcher
;
1231 void GeckoEditableSupport::OnImeRequestCommit() {
1232 AutoGeckoEditableBlocker
blocker(this);
1234 if (mIMEMaskEventsCount
> 0) {
1239 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
1240 if (NS_WARN_IF(!widget
)) {
1244 AutoSelectionRestore
restore(widget
, mDispatcher
);
1246 RemoveComposition(COMMIT_IME_COMPOSITION
);
1249 void GeckoEditableSupport::AsyncNotifyIME(int32_t aNotification
) {
1250 RefPtr
<GeckoEditableSupport
> self(this);
1252 nsAppShell::PostEvent([this, self
, aNotification
] {
1253 if (!mIMEMaskEventsCount
) {
1254 mEditable
->NotifyIME(aNotification
);
1259 nsresult
GeckoEditableSupport::NotifyIME(
1260 TextEventDispatcher
* aTextEventDispatcher
,
1261 const IMENotification
& aNotification
) {
1262 MOZ_ASSERT(mEditable
);
1264 switch (aNotification
.mMessage
) {
1265 case REQUEST_TO_COMMIT_COMPOSITION
: {
1266 ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
1268 RemoveComposition(COMMIT_IME_COMPOSITION
);
1269 AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_COMMIT_COMPOSITION
);
1273 case REQUEST_TO_CANCEL_COMPOSITION
: {
1274 ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
1276 RemoveComposition(CANCEL_IME_COMPOSITION
);
1277 AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_CANCEL_COMPOSITION
);
1281 case NOTIFY_IME_OF_FOCUS
: {
1282 ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
1286 RefPtr
<GeckoEditableSupport
> self(this);
1287 RefPtr
<TextEventDispatcher
> dispatcher
= aTextEventDispatcher
;
1289 // Post an event because we have to flush the text before sending a
1290 // focus event, and we may not be able to flush text during the
1292 nsAppShell::PostEvent([this, self
, dispatcher
] {
1293 nsCOMPtr
<nsIWidget
> widget
= dispatcher
->GetWidget();
1295 --mIMEMaskEventsCount
;
1296 if (!mIMEFocusCount
|| !widget
|| widget
->Destroyed()) {
1300 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN
);
1303 if (!mEditableAttached
) {
1304 // Re-attach on focus; see OnRemovedFrom().
1305 jni::NativeWeakPtrHolder
<GeckoEditableSupport
>::AttachExisting(
1306 mEditable
, do_AddRef(this));
1307 mEditableAttached
= true;
1309 // Because GeckoEditableSupport in content process doesn't
1310 // manage the active input context, we need to retrieve the
1311 // input context from the widget, for use by
1312 // OnImeReplaceText.
1313 mInputContext
= widget
->GetInputContext();
1315 mDispatcher
= dispatcher
;
1316 mIMEKeyEvents
.Clear();
1318 mCachedSelection
.Reset();
1320 mIMEDelaySynchronizeReply
= false;
1321 mIMEActiveCompositionCount
= 0;
1324 // IME will call requestCursorUpdates after getting context.
1325 // So reset cursor update mode before getting context.
1326 mIMEMonitorCursor
= false;
1328 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS
);
1333 case NOTIFY_IME_OF_BLUR
: {
1334 ALOGIME("IME: NOTIFY_IME_OF_BLUR");
1337 MOZ_ASSERT(mIMEFocusCount
>= 0);
1339 RefPtr
<GeckoEditableSupport
> self(this);
1340 nsAppShell::PostEvent([this, self
] {
1341 if (!mIMEFocusCount
) {
1342 mIMEDelaySynchronizeReply
= false;
1343 mIMEActiveSynchronizeCount
= 0;
1344 mIMEActiveCompositionCount
= 0;
1345 mInputContext
.ShutDown();
1346 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_OF_BLUR
);
1347 OnRemovedFrom(mDispatcher
);
1351 // Mask events because we lost focus. Unmask on the next focus.
1352 mIMEMaskEventsCount
++;
1356 case NOTIFY_IME_OF_SELECTION_CHANGE
: {
1357 ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE: SelectionChangeData=%s",
1358 ToString(aNotification
.mSelectionChangeData
).c_str());
1360 if (aNotification
.mSelectionChangeData
.HasRange()) {
1361 mCachedSelection
.mStartOffset
= static_cast<int32_t>(
1362 aNotification
.mSelectionChangeData
.AnchorOffset());
1363 mCachedSelection
.mEndOffset
= static_cast<int32_t>(
1364 aNotification
.mSelectionChangeData
.FocusOffset());
1366 mCachedSelection
.Reset();
1369 PostFlushIMEChanges();
1370 mIMESelectionChanged
= true;
1374 case NOTIFY_IME_OF_TEXT_CHANGE
: {
1375 ALOGIME("IME: NOTIFY_IME_OF_TEXT_CHANGE: TextChangeData=%s",
1376 ToString(aNotification
.mTextChangeData
).c_str());
1378 /* Make sure Java's selection is up-to-date */
1379 PostFlushIMEChanges();
1380 mIMESelectionChanged
= true;
1381 AddIMETextChange(aNotification
.mTextChangeData
);
1385 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
: {
1386 ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
1388 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED isn't sent per IME call.
1389 // Receiving this event means that Gecko has already handled all IME
1390 // composing events in queue.
1393 OnNotifyIMEOfCompositionEventHandled();
1395 // Also, when receiving this event, mIMEDelaySynchronizeReply won't
1396 // update yet on non-e10s case since IME event is posted before updating
1397 // it. So we have to delay handling of this event.
1398 RefPtr
<GeckoEditableSupport
> self(this);
1399 nsAppShell::PostEvent(
1400 [this, self
] { OnNotifyIMEOfCompositionEventHandled(); });
1411 void GeckoEditableSupport::OnNotifyIMEOfCompositionEventHandled() {
1412 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED may be merged with multiple events,
1414 mIMEActiveCompositionCount
= 0;
1415 if (mIMEDelaySynchronizeReply
) {
1419 // Hardware keyboard support requires each string rect.
1420 if (mIMEMonitorCursor
) {
1421 UpdateCompositionRects();
1425 void GeckoEditableSupport::OnRemovedFrom(
1426 TextEventDispatcher
* aTextEventDispatcher
) {
1427 mDispatcher
= nullptr;
1429 if (mIsRemote
&& mEditable
->HasEditableParent()) {
1430 // When we're remote, detach every time.
1431 OnWeakNonIntrusiveDetach(NS_NewRunnableFunction(
1432 "GeckoEditableSupport::OnRemovedFrom",
1433 [editable
= java::GeckoEditableChild::GlobalRef(mEditable
)] {
1434 DisposeNative(editable
);
1439 void GeckoEditableSupport::WillDispatchKeyboardEvent(
1440 TextEventDispatcher
* aTextEventDispatcher
,
1441 WidgetKeyboardEvent
& aKeyboardEvent
, uint32_t aIndexOfKeypress
,
1444 NS_IMETHODIMP_(IMENotificationRequests
)
1445 GeckoEditableSupport::GetIMENotificationRequests() {
1446 return IMENotificationRequests(IMENotificationRequests::NOTIFY_TEXT_CHANGE
);
1449 static bool ShouldKeyboardDismiss(const nsAString
& aInputType
,
1450 const nsAString
& aInputmode
) {
1451 // Some input type uses the prompt to input value. So it is unnecessary to
1452 // show software keyboard.
1453 return aInputmode
.EqualsLiteral("none") || aInputType
.EqualsLiteral("date") ||
1454 aInputType
.EqualsLiteral("time") ||
1455 aInputType
.EqualsLiteral("month") ||
1456 aInputType
.EqualsLiteral("week") ||
1457 aInputType
.EqualsLiteral("datetime-local");
1460 void GeckoEditableSupport::SetInputContext(const InputContext
& aContext
,
1461 const InputContextAction
& aAction
) {
1462 // SetInputContext is called from chrome process only
1463 MOZ_ASSERT(XRE_IsParentProcess());
1464 MOZ_ASSERT(mEditable
);
1467 "IME: SetInputContext: aContext=%s, "
1468 "aAction={mCause=%s, mFocusChange=%s}",
1469 ToString(aContext
).c_str(), ToString(aAction
.mCause
).c_str(),
1470 ToString(aAction
.mFocusChange
).c_str());
1472 mInputContext
= aContext
;
1474 if (mInputContext
.mIMEState
.mEnabled
!= IMEEnabled::Disabled
&&
1475 !ShouldKeyboardDismiss(mInputContext
.mHTMLInputType
,
1476 mInputContext
.mHTMLInputInputmode
) &&
1477 aAction
.UserMightRequestOpenVKB()) {
1478 // Don't reset keyboard when we should simply open the vkb
1479 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_OPEN_VKB
);
1483 // Post an event to keep calls in order relative to NotifyIME.
1484 nsAppShell::PostEvent([this, self
= RefPtr
<GeckoEditableSupport
>(this),
1485 context
= mInputContext
, action
= aAction
] {
1486 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
1488 if (!widget
|| widget
->Destroyed()) {
1491 NotifyIMEContext(context
, action
);
1495 void GeckoEditableSupport::NotifyIMEContext(const InputContext
& aContext
,
1496 const InputContextAction
& aAction
) {
1497 const bool inPrivateBrowsing
= aContext
.mInPrivateBrowsing
;
1498 // isUserAction is used whether opening virtual keyboard. But long press
1499 // shouldn't open it.
1500 const bool isUserAction
=
1501 aAction
.mCause
!= InputContextAction::CAUSE_LONGPRESS
&&
1502 !(aAction
.mCause
== InputContextAction::CAUSE_UNKNOWN_CHROME
&&
1503 aContext
.mIMEState
.mEnabled
== IMEEnabled::Enabled
) &&
1504 (aAction
.IsHandlingUserInput() || aContext
.mHasHandledUserInput
);
1505 const int32_t flags
=
1506 (inPrivateBrowsing
? EditableListener::IME_FLAG_PRIVATE_BROWSING
: 0) |
1507 (isUserAction
? EditableListener::IME_FLAG_USER_ACTION
: 0) |
1508 (aAction
.mFocusChange
== InputContextAction::FOCUS_NOT_CHANGED
1509 ? EditableListener::IME_FOCUS_NOT_CHANGED
1512 mEditable
->NotifyIMEContext(
1513 static_cast<int32_t>(aContext
.mIMEState
.mEnabled
),
1514 aContext
.mHTMLInputType
, aContext
.mHTMLInputInputmode
,
1515 aContext
.mActionHint
, aContext
.mAutocapitalize
, flags
);
1518 InputContext
GeckoEditableSupport::GetInputContext() {
1519 // GetInputContext is called from chrome process only
1520 MOZ_ASSERT(XRE_IsParentProcess());
1521 InputContext context
= mInputContext
;
1522 context
.mIMEState
.mOpen
= IMEState::OPEN_STATE_NOT_SUPPORTED
;
1526 void GeckoEditableSupport::TransferParent(jni::Object::Param aEditableParent
) {
1527 AutoGeckoEditableBlocker
blocker(this);
1529 mEditable
->SetParent(aEditableParent
);
1531 // If we are already focused, make sure the new parent has our token
1532 // and focus information, so it can accept additional calls from us.
1533 if (mIMEFocusCount
> 0) {
1534 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN
);
1536 // GeckoEditableSupport::SetInputContext is called on chrome process
1537 // only, so mInputContext may be still invalid since it is set after
1538 // we have gotton focus.
1539 RefPtr
<GeckoEditableSupport
> self(this);
1540 nsAppShell::PostEvent([self
= std::move(self
)] {
1541 NS_WARNING_ASSERTION(
1543 "Text dispatcher is still null. Why don't we get focus yet?");
1544 self
->NotifyIMEContext(self
->mInputContext
, InputContextAction());
1547 NotifyIMEContext(mInputContext
, InputContextAction());
1549 mEditable
->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS
);
1550 // We have focus, so don't destroy editable child.
1554 if (mIsRemote
&& !mDispatcher
) {
1555 // Detach now if we were only attached temporarily.
1556 OnRemovedFrom(/* dispatcher */ nullptr);
1560 void GeckoEditableSupport::SetOnBrowserChild(dom::BrowserChild
* aBrowserChild
) {
1561 MOZ_ASSERT(!XRE_IsParentProcess());
1562 NS_ENSURE_TRUE_VOID(aBrowserChild
);
1564 const dom::ContentChild
* const contentChild
=
1565 dom::ContentChild::GetSingleton();
1566 RefPtr
<widget::PuppetWidget
> widget(aBrowserChild
->WebWidget());
1567 NS_ENSURE_TRUE_VOID(contentChild
&& widget
);
1569 // Get the content/tab ID in order to get the correct
1570 // IGeckoEditableParent object, which GeckoEditableChild uses to
1571 // communicate with the parent process.
1572 const uint64_t contentId
= contentChild
->GetID();
1573 const uint64_t tabId
= aBrowserChild
->GetTabId();
1574 NS_ENSURE_TRUE_VOID(contentId
&& tabId
);
1576 RefPtr
<widget::TextEventDispatcherListener
> listener
=
1577 widget
->GetNativeTextEventDispatcherListener();
1581 static_cast<widget::TextEventDispatcherListener
*>(widget
)) {
1582 // We need to set a new listener.
1583 const auto editableChild
= java::GeckoEditableChild::New(
1584 /* parent */ nullptr, /* default */ false);
1586 // Temporarily attach so we can receive the initial editable parent.
1587 auto editableSupport
=
1588 jni::NativeWeakPtrHolder
<GeckoEditableSupport
>::Attach(editableChild
,
1590 auto accEditableSupport(editableSupport
.Access());
1591 MOZ_RELEASE_ASSERT(accEditableSupport
);
1593 // Tell PuppetWidget to use our listener for IME operations.
1594 widget
->SetNativeTextEventDispatcherListener(
1595 accEditableSupport
.AsRefPtr().get());
1597 accEditableSupport
->mEditableAttached
= true;
1599 // Connect the new child to a parent that corresponds to the BrowserChild.
1600 java::GeckoServiceChildProcess::GetEditableParent(editableChild
, contentId
,
1605 // We need to update the existing listener to use the new parent.
1607 // We expect the existing TextEventDispatcherListener to be a
1608 // GeckoEditableSupport object, so we perform a sanity check to make
1609 // sure, by comparing their respective vtable pointers.
1610 const RefPtr
<widget::GeckoEditableSupport
> dummy
=
1611 new widget::GeckoEditableSupport(/* child */ nullptr);
1612 NS_ENSURE_TRUE_VOID(*reinterpret_cast<const uintptr_t*>(listener
.get()) ==
1613 *reinterpret_cast<const uintptr_t*>(dummy
.get()));
1615 const auto support
=
1616 static_cast<widget::GeckoEditableSupport
*>(listener
.get());
1617 if (!support
->mEditableAttached
) {
1618 // Temporarily attach so we can receive the initial editable parent.
1619 jni::NativeWeakPtrHolder
<GeckoEditableSupport
>::AttachExisting(
1620 support
->GetJavaEditable(), do_AddRef(support
));
1621 support
->mEditableAttached
= true;
1624 // Transfer to a new parent that corresponds to the BrowserChild.
1625 java::GeckoServiceChildProcess::GetEditableParent(support
->GetJavaEditable(),
1629 nsIWidget
* GeckoEditableSupport::GetWidget() const {
1630 MOZ_ASSERT(NS_IsMainThread());
1631 return mDispatcher
? mDispatcher
->GetWidget() : GetNsWindow();
1634 nsWindow
* GeckoEditableSupport::GetNsWindow() const {
1635 MOZ_ASSERT(NS_IsMainThread());
1637 auto acc(mWindow
.Access());
1642 return acc
->GetNsWindow();
1645 } // namespace widget
1646 } // namespace mozilla