Bug 1755316 - Add audio tests with simultaneous processes r=alwu
[gecko.git] / widget / android / GeckoEditableSupport.cpp
blob295fb332a28d3dfc96fbcee0b2da599dacc48577
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"
10 #include "KeyEvent.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>
33 #ifdef NIGHTLY_BUILD
34 static mozilla::LazyLogModule sGeckoEditableSupportLog("GeckoEditableSupport");
35 # define ALOGIME(...) \
36 MOZ_LOG(sGeckoEditableSupportLog, LogLevel::Debug, (__VA_ARGS__))
37 #else
38 # define ALOGIME(args...) \
39 do { \
40 } while (0)
41 #endif
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)
55 case AKEYCODE_BACK:
56 return NS_VK_ESCAPE;
57 // KEYCODE_CALL (5) ... KEYCODE_POUND (18)
58 case AKEYCODE_DPAD_UP:
59 return NS_VK_UP;
60 case AKEYCODE_DPAD_DOWN:
61 return NS_VK_DOWN;
62 case AKEYCODE_DPAD_LEFT:
63 return NS_VK_LEFT;
64 case AKEYCODE_DPAD_RIGHT:
65 return NS_VK_RIGHT;
66 case AKEYCODE_DPAD_CENTER:
67 return NS_VK_RETURN;
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)
73 case AKEYCODE_COMMA:
74 return NS_VK_COMMA;
75 case AKEYCODE_PERIOD:
76 return NS_VK_PERIOD;
77 case AKEYCODE_ALT_LEFT:
78 return NS_VK_ALT;
79 case AKEYCODE_ALT_RIGHT:
80 return NS_VK_ALT;
81 case AKEYCODE_SHIFT_LEFT:
82 return NS_VK_SHIFT;
83 case AKEYCODE_SHIFT_RIGHT:
84 return NS_VK_SHIFT;
85 case AKEYCODE_TAB:
86 return NS_VK_TAB;
87 case AKEYCODE_SPACE:
88 return NS_VK_SPACE;
89 // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65)
90 case AKEYCODE_ENTER:
91 return NS_VK_RETURN;
92 case AKEYCODE_DEL:
93 return NS_VK_BACK; // Backspace
94 case AKEYCODE_GRAVE:
95 return NS_VK_BACK_QUOTE;
96 // KEYCODE_MINUS (69)
97 case AKEYCODE_EQUALS:
98 return NS_VK_EQUALS;
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)
108 case AKEYCODE_SLASH:
109 return NS_VK_SLASH;
110 // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90)
111 case AKEYCODE_MUTE:
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:
119 return NS_VK_ESCAPE;
120 case AKEYCODE_FORWARD_DEL:
121 return NS_VK_DELETE;
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)
131 case AKEYCODE_SYSRQ:
132 return NS_VK_PRINTSCREEN;
133 case AKEYCODE_BREAK:
134 return NS_VK_PAUSE;
135 case AKEYCODE_MOVE_HOME:
136 return NS_VK_HOME;
137 case AKEYCODE_MOVE_END:
138 return NS_VK_END;
139 case AKEYCODE_INSERT:
140 return NS_VK_INSERT;
141 // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130)
142 case AKEYCODE_F1:
143 return NS_VK_F1;
144 case AKEYCODE_F2:
145 return NS_VK_F2;
146 case AKEYCODE_F3:
147 return NS_VK_F3;
148 case AKEYCODE_F4:
149 return NS_VK_F4;
150 case AKEYCODE_F5:
151 return NS_VK_F5;
152 case AKEYCODE_F6:
153 return NS_VK_F6;
154 case AKEYCODE_F7:
155 return NS_VK_F7;
156 case AKEYCODE_F8:
157 return NS_VK_F8;
158 case AKEYCODE_F9:
159 return NS_VK_F9;
160 case AKEYCODE_F10:
161 return NS_VK_F10;
162 case AKEYCODE_F11:
163 return NS_VK_F11;
164 case AKEYCODE_F12:
165 return NS_VK_F12;
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:
189 return NS_VK_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:
195 return NS_VK_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:
201 return NS_VK_RETURN;
202 case AKEYCODE_NUMPAD_EQUALS:
203 return NS_VK_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:
211 return 0;
212 case AKEYCODE_EISU:
213 return NS_VK_EISU;
214 case AKEYCODE_MUHENKAN:
215 return NS_VK_NONCONVERT;
216 case AKEYCODE_HENKAN:
217 return NS_VK_CONVERT;
218 case AKEYCODE_KATAKANA_HIRAGANA:
219 return 0;
220 case AKEYCODE_YEN:
221 return NS_VK_BACK_SLASH; // Same as other platforms.
222 case AKEYCODE_RO:
223 return NS_VK_BACK_SLASH; // Same as other platforms.
224 case AKEYCODE_KANA:
225 return NS_VK_KANA;
226 case AKEYCODE_ASSIST:
227 return NS_VK_HELP;
229 // the A key is the action key for gamepad devices.
230 case AKEYCODE_BUTTON_A:
231 return NS_VK_RETURN;
233 default:
234 ALOG(
235 "ConvertAndroidKeyCodeToDOMKeyCode: "
236 "No DOM keycode for Android keycode %d",
237 int(androidKeyCode));
238 return 0;
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;
253 switch (keyCode) {
254 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
255 case aNativeKey: \
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
270 case AKEYCODE_SPACE:
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;
354 default:
355 ALOG(
356 "ConvertAndroidKeyCodeToKeyNameIndex: "
357 "No DOM key name index for Android keycode %d",
358 keyCode);
359 return KEY_NAME_INDEX_Unidentified;
363 static CodeNameIndex ConvertAndroidScanCodeToCodeNameIndex(int32_t scanCode) {
364 switch (scanCode) {
365 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
366 case aNativeKey: \
367 return aCodeNameIndex;
369 #include "NativeKeyToDOMCodeName.h"
371 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
373 default:
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,
382 int32_t aFlags) {
383 const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(aKeyCode);
385 aEvent.mModifiers = nsWindow::GetModifiers(aMetaState);
386 aEvent.mKeyCode = domKeyCode;
388 aEvent.mIsRepeat =
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);
401 aEvent.mLocation =
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);
423 return rects;
426 namespace mozilla {
427 namespace widget {
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 {
435 public:
436 explicit AutoGeckoEditableBlocker(GeckoEditableSupport* aGeckoEditableSupport)
437 : mGeckoEditable(aGeckoEditableSupport) {
438 mGeckoEditable->AddBlocker();
440 ~AutoGeckoEditableBlocker() { mGeckoEditable->ReleaseBlocker(); }
442 private:
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()) {
453 return false;
456 nsEventStatus status = nsEventStatus_eIgnore;
458 NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false);
459 mDispatcher->CommitComposition(
460 status, aFlag == CANCEL_IME_COMPOSITION ? &EmptyString() : nullptr);
461 return true;
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()
477 : nullptr;
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.
486 return;
489 EventMessage msg;
490 if (aAction == java::sdk::KeyEvent::ACTION_DOWN) {
491 msg = eKeyDown;
492 } else if (aAction == java::sdk::KeyEvent::ACTION_UP) {
493 msg = eKeyUp;
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");
498 } else {
499 NS_WARNING("Unknown key action event");
500 return;
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()));
518 } else {
519 NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(dispatcher));
520 dispatcher->DispatchKeyboardEvent(msg, event, status);
521 if (widget->Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
522 // Skip default processing.
523 return;
525 mEditable->OnDefaultKeyEvent(aOriginalEvent);
528 // Only send keypress after keydown.
529 if (msg != eKeyDown) {
530 return;
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()));
544 } else {
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,
554 EventMessage msg) {
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"
565 // keyboard event.
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) {
588 // Already posted
589 return;
592 RefPtr<GeckoEditableSupport> self(this);
594 nsAppShell::PostEvent([this, self] {
595 nsCOMPtr<nsIWidget> widget = GetWidget();
596 if (widget && !widget->Destroyed()) {
597 FlushIMEChanges();
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.
610 return;
613 nsCOMPtr<nsIWidget> widget = GetWidget();
614 NS_ENSURE_TRUE_VOID(widget);
616 struct TextRecord {
617 TextRecord() : start(-1), oldEnd(-1), newEnd(-1) {}
619 bool IsValid() const { return start >= 0; }
621 nsString text;
622 int32_t start;
623 int32_t oldEnd;
624 int32_t newEnd;
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) {
635 return false;
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
639 // again.
640 if (aFlags == FLUSH_FLAG_NONE) {
641 FlushIMEChanges(FLUSH_FLAG_RETRY);
642 } else {
643 // Don't retry if already retrying, to avoid infinite loops.
644 __android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport",
645 "Already retrying IME flush");
647 return true;
650 if (mIMEPendingTextChange.IsValid() &&
651 (mIMEPendingTextChange.mStartOffset !=
652 mIMEPendingTextChange.mRemovedEndOffset ||
653 mIMEPendingTextChange.mStartOffset !=
654 mIMEPendingTextChange.mAddedEndOffset)) {
655 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
656 widget);
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()))) {
667 return;
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;
682 int32_t selEnd = -1;
684 if (mIMESelectionChanged) {
685 if (mCachedSelection.IsValid()) {
686 selStart = mCachedSelection.mStartOffset;
687 selEnd = mCachedSelection.mEndOffset;
688 } else {
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
694 // change.
695 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
696 widget);
697 widget->DispatchEvent(&querySelectedTextEvent, status);
699 if (shouldAbort(
700 NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection()))) {
701 return;
704 selStart =
705 static_cast<int32_t>(querySelectedTextEvent.mReply->AnchorOffset());
706 selEnd =
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()) {
722 return false;
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);
730 } else {
731 // Give up because we've already tried.
732 #ifdef RELEASE_OR_BETA
733 env->ExceptionClear();
734 #else
735 MOZ_CATCH_JNI_EXCEPTION(env);
736 #endif
738 return true;
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()) {
748 return;
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;
762 if (mDispatcher) {
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);
768 flushOnException();
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
783 // INT32_MAX.
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;
799 if (composition) {
800 nsEventStatus status = nsEventStatus_eIgnore;
801 uint32_t offset = composition->NativeOffsetOfStartComposition();
802 WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
803 widget);
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>());
811 } else {
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);
823 auto caretRect =
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++;
841 return;
843 if (!mIMEMaskEventsCount) {
844 FlushIMEChanges();
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;
857 OnImeSynchronize();
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.
871 return false;
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();
906 #ifdef NIGHTLY_BUILD
908 nsEventStatus status = nsEventStatus_eIgnore;
909 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
910 widget);
911 widget->DispatchEvent(&querySelectedTextEvent, status);
912 if (querySelectedTextEvent.Succeeded()) {
913 ALOGIME(
914 "IME: Current selection: %s",
915 ToString(querySelectedTextEvent.mReply->mOffsetAndData).c_str());
918 #endif
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
944 // be dispatched.
945 ignoreNextKeyPress = event->mMessage == eKeyDown &&
946 status == nsEventStatus_eConsumeNoDefault;
947 } else {
948 if (ignoreNextKeyPress) {
949 // Don't dispatch key press since previous key down is consumed.
950 ignoreNextKeyPress = false;
951 continue;
953 mDispatcher->MaybeDispatchKeypressEvents(*event, status);
954 if (status == nsEventStatus_eConsumeNoDefault) {
955 textChanged = true;
958 if (!mDispatcher || widget->Destroyed()) {
959 // Don't wait for any text change event.
960 textChanged = false;
961 break;
964 mIMEKeyEvents.Clear();
965 return textChanged;
968 if (aStart != aEnd) {
969 if (composing) {
970 // Actually Gecko doesn't start composition, so it is unnecessary to
971 // delete content before setting composition string.
972 needDispatchCompositionStart = true;
973 } else {
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,
984 false);
985 PostFlushIMEChanges();
986 mIMESelectionChanged = true;
987 AddIMETextChange(dummyChange);
988 textChanged = true;
991 if (StaticPrefs::
992 intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
993 mInputContext.mMayBeIMEUnaware) {
994 SendIMEDummyKeyEvent(widget, eKeyDown);
995 if (!mDispatcher || widget->Destroyed()) {
996 return false;
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()) {
1005 return false;
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()) {
1012 return false;
1014 textChanged = true;
1017 if (composing) {
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++;
1026 textChanged = true;
1028 if (!mDispatcher || widget->Destroyed()) {
1029 return false;
1032 if (StaticPrefs::
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.
1038 return textChanged;
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) {
1048 // Not focused.
1049 return;
1052 TextRange range;
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,
1069 int32_t aFlags) {
1070 AutoGeckoEditableBlocker blocker(this);
1072 if (DoUpdateComposition(aStart, aEnd, aFlags)) {
1073 mIMEDelaySynchronizeReply = true;
1077 bool GeckoEditableSupport::DoUpdateComposition(int32_t aStart, int32_t aEnd,
1078 int32_t aFlags) {
1079 if (mIMEMaskEventsCount > 0) {
1080 // Not focused.
1081 return false;
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.
1095 return false;
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
1115 * unintentionally.
1117 nsString string;
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()) {
1125 if (keepCurrent) {
1126 // Don't start a new composition if we want to keep the current one.
1127 mIMERanges->Clear();
1128 return false;
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,
1146 widget);
1147 widget->DispatchEvent(&querySelectedTextEvent, status);
1148 MOZ_ASSERT(querySelectedTextEvent.Succeeded());
1149 if (querySelectedTextEvent.FoundSelection()) {
1150 string = querySelectedTextEvent.mReply->DataRef();
1153 } else {
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();
1165 return false;
1167 mDispatcher->SetPendingComposition(string, mIMERanges);
1168 mDispatcher->FlushPendingComposition(status);
1169 mIMEActiveCompositionCount++;
1170 mIMERanges->Clear();
1171 return true;
1174 void GeckoEditableSupport::OnImeRequestCursorUpdates(int aRequestMode) {
1175 AutoGeckoEditableBlocker blocker(this);
1177 if (aRequestMode == EditableClient::ONE_SHOT) {
1178 UpdateCompositionRects();
1179 return;
1182 mIMEMonitorCursor = (aRequestMode == EditableClient::START_MONITOR);
1185 class MOZ_STACK_CLASS AutoSelectionRestore final {
1186 public:
1187 explicit AutoSelectionRestore(nsIWidget* widget,
1188 TextEventDispatcher* dispatcher)
1189 : mWidget(widget), mDispatcher(dispatcher) {
1190 MOZ_ASSERT(widget);
1191 if (!dispatcher || !dispatcher->IsComposing()) {
1192 mOffset = UINT32_MAX;
1193 mLength = UINT32_MAX;
1194 return;
1196 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
1197 widget);
1198 nsEventStatus status = nsEventStatus_eIgnore;
1199 widget->DispatchEvent(&querySelectedTextEvent, status);
1200 if (querySelectedTextEvent.DidNotFindSelection()) {
1201 mOffset = UINT32_MAX;
1202 mLength = UINT32_MAX;
1203 return;
1206 mOffset = querySelectedTextEvent.mReply->StartOffset();
1207 mLength = querySelectedTextEvent.mReply->DataLength();
1210 ~AutoSelectionRestore() {
1211 if (mWidget->Destroyed() || mOffset == UINT32_MAX) {
1212 return;
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);
1224 private:
1225 nsCOMPtr<nsIWidget> mWidget;
1226 RefPtr<TextEventDispatcher> mDispatcher;
1227 uint32_t mOffset;
1228 uint32_t mLength;
1231 void GeckoEditableSupport::OnImeRequestCommit() {
1232 AutoGeckoEditableBlocker blocker(this);
1234 if (mIMEMaskEventsCount > 0) {
1235 // Not focused.
1236 return;
1239 nsCOMPtr<nsIWidget> widget = GetWidget();
1240 if (NS_WARN_IF(!widget)) {
1241 return;
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);
1270 break;
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);
1278 break;
1281 case NOTIFY_IME_OF_FOCUS: {
1282 ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
1284 mIMEFocusCount++;
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
1291 // NotifyIME call.
1292 nsAppShell::PostEvent([this, self, dispatcher] {
1293 nsCOMPtr<nsIWidget> widget = dispatcher->GetWidget();
1295 --mIMEMaskEventsCount;
1296 if (!mIMEFocusCount || !widget || widget->Destroyed()) {
1297 return;
1300 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN);
1302 if (mIsRemote) {
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;
1322 FlushIMEText();
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);
1330 break;
1333 case NOTIFY_IME_OF_BLUR: {
1334 ALOGIME("IME: NOTIFY_IME_OF_BLUR");
1336 mIMEFocusCount--;
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++;
1353 break;
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());
1365 } else {
1366 mCachedSelection.Reset();
1369 PostFlushIMEChanges();
1370 mIMESelectionChanged = true;
1371 break;
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);
1382 break;
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.
1392 if (mIsRemote) {
1393 OnNotifyIMEOfCompositionEventHandled();
1394 } else {
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(); });
1402 break;
1405 default:
1406 break;
1408 return NS_OK;
1411 void GeckoEditableSupport::OnNotifyIMEOfCompositionEventHandled() {
1412 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED may be merged with multiple events,
1413 // so reset count.
1414 mIMEActiveCompositionCount = 0;
1415 if (mIMEDelaySynchronizeReply) {
1416 FlushIMEChanges();
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);
1435 }));
1439 void GeckoEditableSupport::WillDispatchKeyboardEvent(
1440 TextEventDispatcher* aTextEventDispatcher,
1441 WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
1442 void* aData) {}
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);
1466 ALOGIME(
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);
1480 return;
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()) {
1489 return;
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
1510 : 0);
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;
1523 return context;
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);
1535 if (mIsRemote) {
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(
1542 self->mDispatcher,
1543 "Text dispatcher is still null. Why don't we get focus yet?");
1544 self->NotifyIMEContext(self->mInputContext, InputContextAction());
1546 } else {
1547 NotifyIMEContext(mInputContext, InputContextAction());
1549 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS);
1550 // We have focus, so don't destroy editable child.
1551 return;
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();
1579 if (!listener ||
1580 listener.get() ==
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,
1589 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,
1601 tabId);
1602 return;
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(),
1626 contentId, tabId);
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());
1638 if (!acc) {
1639 return nullptr;
1642 return acc->GetNsWindow();
1645 } // namespace widget
1646 } // namespace mozilla