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/. */
6
7 #include "GeckoEditableSupport.h"
8
9 #include "AndroidRect.h"
10 #include "KeyEvent.h"
11 #include "PuppetWidget.h"
12 #include "nsIContent.h"
13
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"
28
29 #include <android/api-level.h>
30 #include <android/input.h>
31 #include <android/log.h>
32
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
42
ConvertAndroidKeyCodeToDOMKeyCode(int32_t androidKeyCode)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;
47 }
48
49 if (androidKeyCode >= AKEYCODE_0 && androidKeyCode <= AKEYCODE_9) {
50 return androidKeyCode - AKEYCODE_0 + NS_VK_0;
51 }
52
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)
205
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;
228
229 // the A key is the action key for gamepad devices.
230 case AKEYCODE_BUTTON_A:
231 return NS_VK_RETURN;
232
233 default:
234 ALOG(
235 "ConvertAndroidKeyCodeToDOMKeyCode: "
236 "No DOM keycode for Android keycode %d",
237 int(androidKeyCode));
238 return 0;
239 }
240 }
241
ConvertAndroidKeyCodeToKeyNameIndex(int32_t keyCode,int32_t action,int32_t domPrintableKeyValue)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;
247 }
248
249 if (keyCode >= AKEYCODE_0 && keyCode <= AKEYCODE_9) {
250 return KEY_NAME_INDEX_USE_STRING;
251 }
252
253 switch (keyCode) {
254 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
255 case aNativeKey: \
256 return aKeyNameIndex;
257
258 #include "NativeKeyToDOMKeyName.h"
259
260 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
261
262 // KEYCODE_0 (7) ... KEYCODE_9 (16)
263 case AKEYCODE_STAR: // '*' key
264 case AKEYCODE_POUND: // '#' key
265
266 // KEYCODE_A (29) ... KEYCODE_Z (54)
267
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
282
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:
302
303 case AKEYCODE_YEN: // yen sign key
304 case AKEYCODE_RO: // Japanese Ro key
305 return KEY_NAME_INDEX_USE_STRING;
306
307 case AKEYCODE_NUM: // XXX Not sure
308 case AKEYCODE_PICTSYMBOLS:
309
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:
325
326 case AKEYCODE_MEDIA_CLOSE:
327
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;
345
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;
353
354 default:
355 ALOG(
356 "ConvertAndroidKeyCodeToKeyNameIndex: "
357 "No DOM key name index for Android keycode %d",
358 keyCode);
359 return KEY_NAME_INDEX_Unidentified;
360 }
361 }
362
ConvertAndroidScanCodeToCodeNameIndex(int32_t scanCode)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;
368
369 #include "NativeKeyToDOMCodeName.h"
370
371 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
372
373 default:
374 return CODE_NAME_INDEX_UNKNOWN;
375 }
376 }
377
InitKeyEvent(WidgetKeyboardEvent & aEvent,int32_t aAction,int32_t aKeyCode,int32_t aScanCode,int32_t aMetaState,int64_t aTime,int32_t aDomPrintableKeyValue,int32_t aRepeatCount,int32_t aFlags)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);
384
385 aEvent.mModifiers = nsWindow::GetModifiers(aMetaState);
386 aEvent.mKeyCode = domKeyCode;
387
388 aEvent.mIsRepeat =
389 (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress) &&
390 ((aFlags & java::sdk::KeyEvent::FLAG_LONG_PRESS) || aRepeatCount);
391
392 aEvent.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(
393 aKeyCode, aAction, aDomPrintableKeyValue);
394 aEvent.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(aScanCode);
395
396 if (aEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
397 aDomPrintableKeyValue) {
398 aEvent.mKeyValue = char16_t(aDomPrintableKeyValue);
399 }
400
401 aEvent.mLocation =
402 WidgetKeyboardEvent::ComputeLocationFromCodeValue(aEvent.mCodeNameIndex);
403 aEvent.mTime = aTime;
404 aEvent.mTimeStamp = nsWindow::GetEventTimeStamp(aTime);
405 }
406
ConvertAndroidColor(uint32_t aArgb)407 static nscolor ConvertAndroidColor(uint32_t aArgb) {
408 return NS_RGBA((aArgb & 0x00ff0000) >> 16, (aArgb & 0x0000ff00) >> 8,
409 (aArgb & 0x000000ff), (aArgb & 0xff000000) >> 24);
410 }
411
ConvertRectArrayToJavaRectFArray(const nsTArray<LayoutDeviceIntRect> & aRects)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);
416
417 for (size_t i = 0; i < length; i++) {
418 const LayoutDeviceIntRect& tmp = aRects[i];
419
420 auto rect = java::sdk::RectF::New(tmp.x, tmp.y, tmp.XMost(), tmp.YMost());
421 rects->SetElement(i, rect);
422 }
423 return rects;
424 }
425
426 namespace mozilla {
427 namespace widget {
428
429 NS_IMPL_ISUPPORTS(GeckoEditableSupport, TextEventDispatcherListener,
430 nsISupportsWeakReference)
431
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:
AutoGeckoEditableBlocker(GeckoEditableSupport * aGeckoEditableSupport)436 explicit AutoGeckoEditableBlocker(GeckoEditableSupport* aGeckoEditableSupport)
437 : mGeckoEditable(aGeckoEditableSupport) {
438 mGeckoEditable->AddBlocker();
439 }
~AutoGeckoEditableBlocker()440 ~AutoGeckoEditableBlocker() { mGeckoEditable->ReleaseBlocker(); }
441
442 private:
443 RefPtr<GeckoEditableSupport> mGeckoEditable;
444 };
445
GetComposition() const446 RefPtr<TextComposition> GeckoEditableSupport::GetComposition() const {
447 nsCOMPtr<nsIWidget> widget = GetWidget();
448 return widget ? IMEStateManager::GetTextCompositionFor(widget) : nullptr;
449 }
450
RemoveComposition(RemoveCompositionFlag aFlag)451 bool GeckoEditableSupport::RemoveComposition(RemoveCompositionFlag aFlag) {
452 if (!mDispatcher || !mDispatcher->IsComposing()) {
453 return false;
454 }
455
456 nsEventStatus status = nsEventStatus_eIgnore;
457
458 NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false);
459 mDispatcher->CommitComposition(
460 status, aFlag == CANCEL_IME_COMPOSITION ? &EmptyString() : nullptr);
461 return true;
462 }
463
OnKeyEvent(int32_t aAction,int32_t aKeyCode,int32_t aScanCode,int32_t aMetaState,int32_t aKeyPressMetaState,int64_t aTime,int32_t aDomPrintableKeyValue,int32_t aRepeatCount,int32_t aFlags,bool aIsSynthesizedImeKey,jni::Object::Param aOriginalEvent)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);
472
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);
479
480 if (!aIsSynthesizedImeKey) {
481 if (nsWindow* window = GetNsWindow()) {
482 window->UserActivity();
483 }
484 } else if (aIsSynthesizedImeKey && mIMEMaskEventsCount > 0) {
485 // Don't synthesize editor keys when not focused.
486 return;
487 }
488
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;
501 }
502
503 nsEventStatus status = nsEventStatus_eIgnore;
504 WidgetKeyboardEvent event(true, msg, widget);
505 InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime,
506 aDomPrintableKeyValue, aRepeatCount, aFlags);
507
508 if (nsIWidget::UsePuppetWidgets()) {
509 // Don't use native key bindings.
510 event.PreventNativeKeyBindings();
511 }
512
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;
524 }
525 mEditable->OnDefaultKeyEvent(aOriginalEvent);
526 }
527
528 // Only send keypress after keydown.
529 if (msg != eKeyDown) {
530 return;
531 }
532
533 WidgetKeyboardEvent pressEvent(true, eKeyPress, widget);
534 InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aKeyPressMetaState,
535 aTime, aDomPrintableKeyValue, aRepeatCount, aFlags);
536
537 if (nsIWidget::UsePuppetWidgets()) {
538 // Don't use native key bindings.
539 pressEvent.PreventNativeKeyBindings();
540 }
541
542 if (aIsSynthesizedImeKey) {
543 mIMEKeyEvents.AppendElement(UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
544 } else {
545 dispatcher->MaybeDispatchKeypressEvents(pressEvent, status);
546 }
547 }
548
549 /*
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.
552 */
SendIMEDummyKeyEvent(nsIWidget * aWidget,EventMessage msg)553 void GeckoEditableSupport::SendIMEDummyKeyEvent(nsIWidget* aWidget,
554 EventMessage msg) {
555 AutoGeckoEditableBlocker blocker(this);
556
557 nsEventStatus status = nsEventStatus_eIgnore;
558 MOZ_ASSERT(mDispatcher);
559
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);
575 }
576
AddIMETextChange(const IMENotification::TextChangeDataBase & aChange)577 void GeckoEditableSupport::AddIMETextChange(
578 const IMENotification::TextChangeDataBase& aChange) {
579 mIMEPendingTextChange.MergeWith(aChange);
580
581 // We may not be in the middle of flushing,
582 // in which case this flag is meaningless.
583 mIMETextChangedDuringFlush = true;
584 }
585
PostFlushIMEChanges()586 void GeckoEditableSupport::PostFlushIMEChanges() {
587 if (mIMEPendingTextChange.IsValid() || mIMESelectionChanged) {
588 // Already posted
589 return;
590 }
591
592 RefPtr<GeckoEditableSupport> self(this);
593
594 nsAppShell::PostEvent([this, self] {
595 nsCOMPtr<nsIWidget> widget = GetWidget();
596 if (widget && !widget->Destroyed()) {
597 FlushIMEChanges();
598 }
599 });
600 }
601
FlushIMEChanges(FlushChangesFlag aFlags)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);
606
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;
611 }
612
613 nsCOMPtr<nsIWidget> widget = GetWidget();
614 NS_ENSURE_TRUE_VOID(widget);
615
616 struct TextRecord {
617 TextRecord() : start(-1), oldEnd(-1), newEnd(-1) {}
618
619 bool IsValid() const { return start >= 0; }
620
621 nsString text;
622 int32_t start;
623 int32_t oldEnd;
624 int32_t newEnd;
625 };
626 TextRecord textTransaction;
627
628 nsEventStatus status = nsEventStatus_eIgnore;
629 bool causedOnlyByComposition = mIMEPendingTextChange.IsValid() &&
630 mIMEPendingTextChange.mCausedOnlyByComposition;
631 mIMETextChangedDuringFlush = false;
632
633 auto shouldAbort = [=](bool aForce) -> bool {
634 if (!aForce && !mIMETextChangedDuringFlush) {
635 return false;
636 }
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");
646 }
647 return true;
648 };
649
650 if (mIMEPendingTextChange.IsValid() &&
651 (mIMEPendingTextChange.mStartOffset !=
652 mIMEPendingTextChange.mRemovedEndOffset ||
653 mIMEPendingTextChange.mStartOffset !=
654 mIMEPendingTextChange.mAddedEndOffset)) {
655 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
656 widget);
657
658 if (mIMEPendingTextChange.mAddedEndOffset !=
659 mIMEPendingTextChange.mStartOffset) {
660 queryTextContentEvent.InitForQueryTextContent(
661 mIMEPendingTextChange.mStartOffset,
662 mIMEPendingTextChange.mAddedEndOffset -
663 mIMEPendingTextChange.mStartOffset);
664 widget->DispatchEvent(&queryTextContentEvent, status);
665
666 if (shouldAbort(NS_WARN_IF(queryTextContentEvent.Failed()))) {
667 return;
668 }
669
670 textTransaction.text = queryTextContentEvent.mReply->DataRef();
671 }
672
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);
679 }
680
681 int32_t selStart = -1;
682 int32_t selEnd = -1;
683
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);
698
699 if (shouldAbort(
700 NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection()))) {
701 return;
702 }
703
704 selStart =
705 static_cast<int32_t>(querySelectedTextEvent.mReply->AnchorOffset());
706 selEnd =
707 static_cast<int32_t>(querySelectedTextEvent.mReply->FocusOffset());
708 }
709
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);
716 }
717 }
718
719 JNIEnv* const env = jni::GetGeckoThreadEnv();
720 auto flushOnException = [=]() -> bool {
721 if (!env->ExceptionCheck()) {
722 return false;
723 }
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
737 }
738 return true;
739 };
740
741 // Commit the text change and selection change transaction.
742 mIMEPendingTextChange.Clear();
743
744 if (textTransaction.IsValid()) {
745 mEditable->OnTextChange(textTransaction.text, textTransaction.start,
746 textTransaction.oldEnd, textTransaction.newEnd);
747 if (flushOnException()) {
748 return;
749 }
750 }
751
752 while (mIMEDelaySynchronizeReply && mIMEActiveSynchronizeCount) {
753 mIMEActiveSynchronizeCount--;
754 mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT);
755 }
756 mIMEDelaySynchronizeReply = false;
757 mIMEActiveSynchronizeCount = 0;
758 mIMEActiveCompositionCount = 0;
759
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();
766 }
767 mEditable->OnSelectionChange(selStart, selEnd, causedOnlyByComposition);
768 flushOnException();
769 }
770 }
771
FlushIMEText(FlushChangesFlag aFlags)772 void GeckoEditableSupport::FlushIMEText(FlushChangesFlag aFlags) {
773 NS_WARNING_ASSERTION(
774 !mIMEDelaySynchronizeReply || !mIMEActiveCompositionCount,
775 "Cannot synchronize Java text with Gecko text");
776
777 // Notify Java of the newly focused content
778 mIMEPendingTextChange.Clear();
779 mIMESelectionChanged = true;
780
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);
789
790 FlushIMEChanges(aFlags);
791 }
792
UpdateCompositionRects()793 void GeckoEditableSupport::UpdateCompositionRects() {
794 nsCOMPtr<nsIWidget> widget = GetWidget();
795 RefPtr<TextComposition> composition(GetComposition());
796 NS_ENSURE_TRUE_VOID(mDispatcher && widget);
797
798 if (!composition) {
799 return;
800 }
801
802 nsEventStatus status = nsEventStatus_eIgnore;
803 uint32_t offset = composition->NativeOffsetOfStartComposition();
804 WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
805 widget);
806 queryTextRectsEvent.InitForQueryTextRectArray(offset,
807 composition->String().Length());
808 widget->DispatchEvent(&queryTextRectsEvent, status);
809
810 auto rects = ConvertRectArrayToJavaRectFArray(
811 queryTextRectsEvent.Succeeded()
812 ? queryTextRectsEvent.mReply->mRectArray
813 : CopyableTArray<mozilla::LayoutDeviceIntRect>());
814
815 mEditable->UpdateCompositionRects(rects);
816 }
817
OnImeSynchronize()818 void GeckoEditableSupport::OnImeSynchronize() {
819 AutoGeckoEditableBlocker blocker(this);
820
821 if (mIMEDelaySynchronizeReply) {
822 // If we are waiting for other events to reply,
823 // queue this reply as well.
824 mIMEActiveSynchronizeCount++;
825 return;
826 }
827 if (!mIMEMaskEventsCount) {
828 FlushIMEChanges();
829 }
830 mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT);
831 }
832
OnImeReplaceText(int32_t aStart,int32_t aEnd,jni::String::Param aText)833 void GeckoEditableSupport::OnImeReplaceText(int32_t aStart, int32_t aEnd,
834 jni::String::Param aText) {
835 AutoGeckoEditableBlocker blocker(this);
836
837 if (DoReplaceText(aStart, aEnd, aText)) {
838 mIMEDelaySynchronizeReply = true;
839 }
840
841 OnImeSynchronize();
842 }
843
DoReplaceText(int32_t aStart,int32_t aEnd,jni::String::Param aText)844 bool GeckoEditableSupport::DoReplaceText(int32_t aStart, int32_t aEnd,
845 jni::String::Param aText) {
846 ALOGIME("IME: IME_REPLACE_TEXT: text=\"%s\"",
847 NS_ConvertUTF16toUTF8(aText->ToString()).get());
848
849 // Return true if processed and we should reply to the OnImeReplaceText
850 // event later. Return false if _not_ processed and we should reply to the
851 // OnImeReplaceText event now.
852
853 if (mIMEMaskEventsCount > 0) {
854 // Not focused; still reply to events, but don't do anything else.
855 return false;
856 }
857
858 if (nsWindow* window = GetNsWindow()) {
859 window->UserActivity();
860 }
861
862 /*
863 Replace text in Gecko thread from aStart to aEnd with the string text.
864 */
865 nsCOMPtr<nsIWidget> widget = GetWidget();
866 NS_ENSURE_TRUE(mDispatcher && widget, false);
867 NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false);
868
869 RefPtr<TextComposition> composition(GetComposition());
870 MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
871
872 nsString string(aText->ToString());
873 const bool composing = !mIMERanges->IsEmpty();
874 nsEventStatus status = nsEventStatus_eIgnore;
875 bool textChanged = composing;
876 // Whether deleting content before setting or committing composition text.
877 bool performDeletion = false;
878 // Dispatch composition start to set current composition.
879 bool needDispatchCompositionStart = false;
880
881 if (!mIMEKeyEvents.IsEmpty() || !composition || !mDispatcher->IsComposing() ||
882 uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
883 uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
884 composition->String().Length()) {
885 // Only start a new composition if we have key events,
886 // if we don't have an existing composition, or
887 // the replaced text does not match our composition.
888 textChanged |= RemoveComposition();
889
890 #ifdef NIGHTLY_BUILD
891 {
892 nsEventStatus status = nsEventStatus_eIgnore;
893 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
894 widget);
895 widget->DispatchEvent(&querySelectedTextEvent, status);
896 if (querySelectedTextEvent.Succeeded()) {
897 ALOGIME(
898 "IME: Current selection: %s",
899 ToString(querySelectedTextEvent.mReply->mOffsetAndData).c_str());
900 }
901 }
902 #endif
903
904 // If aStart or aEnd is negative value, we use current selection instead
905 // of updating the selection.
906 if (aStart >= 0 && aEnd >= 0) {
907 // Use text selection to set target position(s) for
908 // insert, or replace, of text.
909 WidgetSelectionEvent event(true, eSetSelection, widget);
910 event.mOffset = uint32_t(aStart);
911 event.mLength = uint32_t(aEnd - aStart);
912 event.mExpandToClusterBoundary = false;
913 event.mReason = nsISelectionListener::IME_REASON;
914 widget->DispatchEvent(&event, status);
915 }
916
917 if (!mIMEKeyEvents.IsEmpty()) {
918 bool ignoreNextKeyPress = false;
919 for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
920 const auto event = mIMEKeyEvents[i]->AsKeyboardEvent();
921 // widget for duplicated events is initially nullptr.
922 event->mWidget = widget;
923
924 status = nsEventStatus_eIgnore;
925 if (event->mMessage != eKeyPress) {
926 mDispatcher->DispatchKeyboardEvent(event->mMessage, *event, status);
927 // Skip default processing. It means that next key press shouldn't
928 // be dispatched.
929 ignoreNextKeyPress = event->mMessage == eKeyDown &&
930 status == nsEventStatus_eConsumeNoDefault;
931 } else {
932 if (ignoreNextKeyPress) {
933 // Don't dispatch key press since previous key down is consumed.
934 ignoreNextKeyPress = false;
935 continue;
936 }
937 mDispatcher->MaybeDispatchKeypressEvents(*event, status);
938 if (status == nsEventStatus_eConsumeNoDefault) {
939 textChanged = true;
940 }
941 }
942 if (!mDispatcher || widget->Destroyed()) {
943 // Don't wait for any text change event.
944 textChanged = false;
945 break;
946 }
947 }
948 mIMEKeyEvents.Clear();
949 return textChanged;
950 }
951
952 if (aStart != aEnd) {
953 if (composing) {
954 // Actually Gecko doesn't start composition, so it is unnecessary to
955 // delete content before setting composition string.
956 needDispatchCompositionStart = true;
957 } else {
958 // Perform a deletion first.
959 performDeletion = true;
960 }
961 }
962 } else if (composition->String().Equals(string)) {
963 /* If the new text is the same as the existing composition text,
964 * the NS_COMPOSITION_CHANGE event does not generate a text
965 * change notification. However, the Java side still expects
966 * one, so we manually generate a notification. */
967 IMENotification::TextChangeData dummyChange(aStart, aEnd, aEnd, false,
968 false);
969 PostFlushIMEChanges();
970 mIMESelectionChanged = true;
971 AddIMETextChange(dummyChange);
972 textChanged = true;
973 }
974
975 if (StaticPrefs::
976 intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
977 mInputContext.mMayBeIMEUnaware) {
978 SendIMEDummyKeyEvent(widget, eKeyDown);
979 if (!mDispatcher || widget->Destroyed()) {
980 return false;
981 }
982 }
983
984 if (needDispatchCompositionStart) {
985 // StartComposition sets composition string from selected string.
986 nsEventStatus status = nsEventStatus_eIgnore;
987 mDispatcher->StartComposition(status);
988 if (!mDispatcher || widget->Destroyed()) {
989 return false;
990 }
991 } else if (performDeletion) {
992 WidgetContentCommandEvent event(true, eContentCommandDelete, widget);
993 event.mTime = PR_Now() / 1000;
994 widget->DispatchEvent(&event, status);
995 if (!mDispatcher || widget->Destroyed()) {
996 return false;
997 }
998 textChanged = true;
999 }
1000
1001 if (composing) {
1002 mDispatcher->SetPendingComposition(string, mIMERanges);
1003 mDispatcher->FlushPendingComposition(status);
1004 mIMEActiveCompositionCount++;
1005 // Ensure IME ranges are empty.
1006 mIMERanges->Clear();
1007 } else if (!string.IsEmpty() || mDispatcher->IsComposing()) {
1008 mDispatcher->CommitComposition(status, &string);
1009 mIMEActiveCompositionCount++;
1010 textChanged = true;
1011 }
1012 if (!mDispatcher || widget->Destroyed()) {
1013 return false;
1014 }
1015
1016 if (StaticPrefs::
1017 intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
1018 mInputContext.mMayBeIMEUnaware) {
1019 SendIMEDummyKeyEvent(widget, eKeyUp);
1020 // Widget may be destroyed after dispatching the above event.
1021 }
1022 return textChanged;
1023 }
1024
OnImeAddCompositionRange(int32_t aStart,int32_t aEnd,int32_t aRangeType,int32_t aRangeStyle,int32_t aRangeLineStyle,bool aRangeBoldLine,int32_t aRangeForeColor,int32_t aRangeBackColor,int32_t aRangeLineColor)1025 void GeckoEditableSupport::OnImeAddCompositionRange(
1026 int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle,
1027 int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor,
1028 int32_t aRangeBackColor, int32_t aRangeLineColor) {
1029 AutoGeckoEditableBlocker blocker(this);
1030
1031 if (mIMEMaskEventsCount > 0) {
1032 // Not focused.
1033 return;
1034 }
1035
1036 TextRange range;
1037 range.mStartOffset = aStart;
1038 range.mEndOffset = aEnd;
1039 range.mRangeType = ToTextRangeType(aRangeType);
1040 range.mRangeStyle.mDefinedStyles = aRangeStyle;
1041 range.mRangeStyle.mLineStyle = TextRangeStyle::ToLineStyle(aRangeLineStyle);
1042 range.mRangeStyle.mIsBoldLine = aRangeBoldLine;
1043 range.mRangeStyle.mForegroundColor =
1044 ConvertAndroidColor(uint32_t(aRangeForeColor));
1045 range.mRangeStyle.mBackgroundColor =
1046 ConvertAndroidColor(uint32_t(aRangeBackColor));
1047 range.mRangeStyle.mUnderlineColor =
1048 ConvertAndroidColor(uint32_t(aRangeLineColor));
1049 mIMERanges->AppendElement(range);
1050 }
1051
OnImeUpdateComposition(int32_t aStart,int32_t aEnd,int32_t aFlags)1052 void GeckoEditableSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd,
1053 int32_t aFlags) {
1054 AutoGeckoEditableBlocker blocker(this);
1055
1056 if (DoUpdateComposition(aStart, aEnd, aFlags)) {
1057 mIMEDelaySynchronizeReply = true;
1058 }
1059 }
1060
DoUpdateComposition(int32_t aStart,int32_t aEnd,int32_t aFlags)1061 bool GeckoEditableSupport::DoUpdateComposition(int32_t aStart, int32_t aEnd,
1062 int32_t aFlags) {
1063 if (mIMEMaskEventsCount > 0) {
1064 // Not focused.
1065 return false;
1066 }
1067
1068 nsCOMPtr<nsIWidget> widget = GetWidget();
1069 nsEventStatus status = nsEventStatus_eIgnore;
1070 NS_ENSURE_TRUE(mDispatcher && widget, false);
1071
1072 const bool keepCurrent =
1073 !!(aFlags & java::GeckoEditableChild::FLAG_KEEP_CURRENT_COMPOSITION);
1074
1075 // A composition with no ranges means we want to set the selection.
1076 if (mIMERanges->IsEmpty()) {
1077 if (keepCurrent && mDispatcher->IsComposing()) {
1078 // Don't set selection if we want to keep current composition.
1079 return false;
1080 }
1081
1082 MOZ_ASSERT(aStart >= 0 && aEnd >= 0);
1083 const bool compositionChanged = RemoveComposition();
1084
1085 WidgetSelectionEvent selEvent(true, eSetSelection, widget);
1086 selEvent.mOffset = std::min(aStart, aEnd);
1087 selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
1088 selEvent.mReversed = aStart > aEnd;
1089 selEvent.mExpandToClusterBoundary = false;
1090 widget->DispatchEvent(&selEvent, status);
1091 return compositionChanged;
1092 }
1093
1094 /**
1095 * Update the composition from aStart to aEnd using information from added
1096 * ranges. This is only used for visual indication and does not affect the
1097 * text content. Only the offsets are specified and not the text content
1098 * to eliminate the possibility of this event altering the text content
1099 * unintentionally.
1100 */
1101 nsString string;
1102 RefPtr<TextComposition> composition(GetComposition());
1103 MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
1104
1105 if (!composition || !mDispatcher->IsComposing() ||
1106 uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
1107 uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
1108 composition->String().Length()) {
1109 if (keepCurrent) {
1110 // Don't start a new composition if we want to keep the current one.
1111 mIMERanges->Clear();
1112 return false;
1113 }
1114
1115 // Only start new composition if we don't have an existing one,
1116 // or if the existing composition doesn't match the new one.
1117 RemoveComposition();
1118
1119 {
1120 WidgetSelectionEvent event(true, eSetSelection, widget);
1121 event.mOffset = uint32_t(aStart);
1122 event.mLength = uint32_t(aEnd - aStart);
1123 event.mExpandToClusterBoundary = false;
1124 event.mReason = nsISelectionListener::IME_REASON;
1125 widget->DispatchEvent(&event, status);
1126 }
1127
1128 {
1129 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
1130 widget);
1131 widget->DispatchEvent(&querySelectedTextEvent, status);
1132 MOZ_ASSERT(querySelectedTextEvent.Succeeded());
1133 if (querySelectedTextEvent.FoundSelection()) {
1134 string = querySelectedTextEvent.mReply->DataRef();
1135 }
1136 }
1137 } else {
1138 // If the new composition matches the existing composition,
1139 // reuse the old composition.
1140 string = composition->String();
1141 }
1142
1143 ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%zu, range=%zu",
1144 NS_ConvertUTF16toUTF8(string).get(), string.Length(),
1145 mIMERanges->Length());
1146
1147 if (NS_WARN_IF(NS_FAILED(BeginInputTransaction(mDispatcher)))) {
1148 mIMERanges->Clear();
1149 return false;
1150 }
1151 mDispatcher->SetPendingComposition(string, mIMERanges);
1152 mDispatcher->FlushPendingComposition(status);
1153 mIMEActiveCompositionCount++;
1154 mIMERanges->Clear();
1155 return true;
1156 }
1157
OnImeRequestCursorUpdates(int aRequestMode)1158 void GeckoEditableSupport::OnImeRequestCursorUpdates(int aRequestMode) {
1159 AutoGeckoEditableBlocker blocker(this);
1160
1161 if (aRequestMode == EditableClient::ONE_SHOT) {
1162 UpdateCompositionRects();
1163 return;
1164 }
1165
1166 mIMEMonitorCursor = (aRequestMode == EditableClient::START_MONITOR);
1167 }
1168
1169 class MOZ_STACK_CLASS AutoSelectionRestore final {
1170 public:
AutoSelectionRestore(nsIWidget * widget,TextEventDispatcher * dispatcher)1171 explicit AutoSelectionRestore(nsIWidget* widget,
1172 TextEventDispatcher* dispatcher)
1173 : mWidget(widget), mDispatcher(dispatcher) {
1174 MOZ_ASSERT(widget);
1175 if (!dispatcher || !dispatcher->IsComposing()) {
1176 mOffset = UINT32_MAX;
1177 mLength = UINT32_MAX;
1178 return;
1179 }
1180 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
1181 widget);
1182 nsEventStatus status = nsEventStatus_eIgnore;
1183 widget->DispatchEvent(&querySelectedTextEvent, status);
1184 if (querySelectedTextEvent.DidNotFindSelection()) {
1185 mOffset = UINT32_MAX;
1186 mLength = UINT32_MAX;
1187 return;
1188 }
1189
1190 mOffset = querySelectedTextEvent.mReply->StartOffset();
1191 mLength = querySelectedTextEvent.mReply->DataLength();
1192 }
1193
~AutoSelectionRestore()1194 ~AutoSelectionRestore() {
1195 if (mWidget->Destroyed() || mOffset == UINT32_MAX) {
1196 return;
1197 }
1198
1199 WidgetSelectionEvent selection(true, eSetSelection, mWidget);
1200 selection.mOffset = mOffset;
1201 selection.mLength = mLength;
1202 selection.mExpandToClusterBoundary = false;
1203 selection.mReason = nsISelectionListener::IME_REASON;
1204 nsEventStatus status = nsEventStatus_eIgnore;
1205 mWidget->DispatchEvent(&selection, status);
1206 }
1207
1208 private:
1209 nsCOMPtr<nsIWidget> mWidget;
1210 RefPtr<TextEventDispatcher> mDispatcher;
1211 uint32_t mOffset;
1212 uint32_t mLength;
1213 };
1214
OnImeRequestCommit()1215 void GeckoEditableSupport::OnImeRequestCommit() {
1216 AutoGeckoEditableBlocker blocker(this);
1217
1218 if (mIMEMaskEventsCount > 0) {
1219 // Not focused.
1220 return;
1221 }
1222
1223 nsCOMPtr<nsIWidget> widget = GetWidget();
1224 if (NS_WARN_IF(!widget)) {
1225 return;
1226 }
1227
1228 AutoSelectionRestore restore(widget, mDispatcher);
1229
1230 RemoveComposition(COMMIT_IME_COMPOSITION);
1231 }
1232
AsyncNotifyIME(int32_t aNotification)1233 void GeckoEditableSupport::AsyncNotifyIME(int32_t aNotification) {
1234 RefPtr<GeckoEditableSupport> self(this);
1235
1236 nsAppShell::PostEvent([this, self, aNotification] {
1237 if (!mIMEMaskEventsCount) {
1238 mEditable->NotifyIME(aNotification);
1239 }
1240 });
1241 }
1242
NotifyIME(TextEventDispatcher * aTextEventDispatcher,const IMENotification & aNotification)1243 nsresult GeckoEditableSupport::NotifyIME(
1244 TextEventDispatcher* aTextEventDispatcher,
1245 const IMENotification& aNotification) {
1246 MOZ_ASSERT(mEditable);
1247
1248 switch (aNotification.mMessage) {
1249 case REQUEST_TO_COMMIT_COMPOSITION: {
1250 ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
1251
1252 RemoveComposition(COMMIT_IME_COMPOSITION);
1253 AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_COMMIT_COMPOSITION);
1254 break;
1255 }
1256
1257 case REQUEST_TO_CANCEL_COMPOSITION: {
1258 ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
1259
1260 RemoveComposition(CANCEL_IME_COMPOSITION);
1261 AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_CANCEL_COMPOSITION);
1262 break;
1263 }
1264
1265 case NOTIFY_IME_OF_FOCUS: {
1266 ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
1267
1268 mIMEFocusCount++;
1269
1270 RefPtr<GeckoEditableSupport> self(this);
1271 RefPtr<TextEventDispatcher> dispatcher = aTextEventDispatcher;
1272
1273 // Post an event because we have to flush the text before sending a
1274 // focus event, and we may not be able to flush text during the
1275 // NotifyIME call.
1276 nsAppShell::PostEvent([this, self, dispatcher] {
1277 nsCOMPtr<nsIWidget> widget = dispatcher->GetWidget();
1278
1279 --mIMEMaskEventsCount;
1280 if (!mIMEFocusCount || !widget || widget->Destroyed()) {
1281 return;
1282 }
1283
1284 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN);
1285
1286 if (mIsRemote) {
1287 if (!mEditableAttached) {
1288 // Re-attach on focus; see OnRemovedFrom().
1289 jni::NativeWeakPtrHolder<GeckoEditableSupport>::AttachExisting(
1290 mEditable, do_AddRef(this));
1291 mEditableAttached = true;
1292 }
1293 // Because GeckoEditableSupport in content process doesn't
1294 // manage the active input context, we need to retrieve the
1295 // input context from the widget, for use by
1296 // OnImeReplaceText.
1297 mInputContext = widget->GetInputContext();
1298 }
1299 mDispatcher = dispatcher;
1300 mIMEKeyEvents.Clear();
1301
1302 mCachedSelection.Reset();
1303
1304 mIMEDelaySynchronizeReply = false;
1305 mIMEActiveCompositionCount = 0;
1306 FlushIMEText();
1307
1308 // IME will call requestCursorUpdates after getting context.
1309 // So reset cursor update mode before getting context.
1310 mIMEMonitorCursor = false;
1311
1312 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS);
1313 });
1314 break;
1315 }
1316
1317 case NOTIFY_IME_OF_BLUR: {
1318 ALOGIME("IME: NOTIFY_IME_OF_BLUR");
1319
1320 mIMEFocusCount--;
1321 MOZ_ASSERT(mIMEFocusCount >= 0);
1322
1323 RefPtr<GeckoEditableSupport> self(this);
1324 nsAppShell::PostEvent([this, self] {
1325 if (!mIMEFocusCount) {
1326 mIMEDelaySynchronizeReply = false;
1327 mIMEActiveSynchronizeCount = 0;
1328 mIMEActiveCompositionCount = 0;
1329 mInputContext.ShutDown();
1330 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_BLUR);
1331 OnRemovedFrom(mDispatcher);
1332 }
1333 });
1334
1335 // Mask events because we lost focus. Unmask on the next focus.
1336 mIMEMaskEventsCount++;
1337 break;
1338 }
1339
1340 case NOTIFY_IME_OF_SELECTION_CHANGE: {
1341 ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE: SelectionChangeData=%s",
1342 ToString(aNotification.mSelectionChangeData).c_str());
1343
1344 if (aNotification.mSelectionChangeData.HasRange()) {
1345 mCachedSelection.mStartOffset = static_cast<int32_t>(
1346 aNotification.mSelectionChangeData.AnchorOffset());
1347 mCachedSelection.mEndOffset = static_cast<int32_t>(
1348 aNotification.mSelectionChangeData.FocusOffset());
1349 } else {
1350 mCachedSelection.Reset();
1351 }
1352
1353 PostFlushIMEChanges();
1354 mIMESelectionChanged = true;
1355 break;
1356 }
1357
1358 case NOTIFY_IME_OF_TEXT_CHANGE: {
1359 ALOGIME("IME: NOTIFY_IME_OF_TEXT_CHANGE: TextChangeData=%s",
1360 ToString(aNotification.mTextChangeData).c_str());
1361
1362 /* Make sure Java's selection is up-to-date */
1363 PostFlushIMEChanges();
1364 mIMESelectionChanged = true;
1365 AddIMETextChange(aNotification.mTextChangeData);
1366 break;
1367 }
1368
1369 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
1370 ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
1371
1372 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED isn't sent per IME call.
1373 // Receiving this event means that Gecko has already handled all IME
1374 // composing events in queue.
1375 //
1376 if (mIsRemote) {
1377 OnNotifyIMEOfCompositionEventHandled();
1378 } else {
1379 // Also, when receiving this event, mIMEDelaySynchronizeReply won't
1380 // update yet on non-e10s case since IME event is posted before updating
1381 // it. So we have to delay handling of this event.
1382 RefPtr<GeckoEditableSupport> self(this);
1383 nsAppShell::PostEvent(
1384 [this, self] { OnNotifyIMEOfCompositionEventHandled(); });
1385 }
1386 break;
1387 }
1388
1389 default:
1390 break;
1391 }
1392 return NS_OK;
1393 }
1394
OnNotifyIMEOfCompositionEventHandled()1395 void GeckoEditableSupport::OnNotifyIMEOfCompositionEventHandled() {
1396 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED may be merged with multiple events,
1397 // so reset count.
1398 mIMEActiveCompositionCount = 0;
1399 if (mIMEDelaySynchronizeReply) {
1400 FlushIMEChanges();
1401 }
1402
1403 // Hardware keyboard support requires each string rect.
1404 if (mIMEMonitorCursor) {
1405 UpdateCompositionRects();
1406 }
1407 }
1408
OnRemovedFrom(TextEventDispatcher * aTextEventDispatcher)1409 void GeckoEditableSupport::OnRemovedFrom(
1410 TextEventDispatcher* aTextEventDispatcher) {
1411 mDispatcher = nullptr;
1412
1413 if (mIsRemote && mEditable->HasEditableParent()) {
1414 // When we're remote, detach every time.
1415 OnWeakNonIntrusiveDetach(NS_NewRunnableFunction(
1416 "GeckoEditableSupport::OnRemovedFrom",
1417 [editable = java::GeckoEditableChild::GlobalRef(mEditable)] {
1418 DisposeNative(editable);
1419 }));
1420 }
1421 }
1422
WillDispatchKeyboardEvent(TextEventDispatcher * aTextEventDispatcher,WidgetKeyboardEvent & aKeyboardEvent,uint32_t aIndexOfKeypress,void * aData)1423 void GeckoEditableSupport::WillDispatchKeyboardEvent(
1424 TextEventDispatcher* aTextEventDispatcher,
1425 WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
1426 void* aData) {}
1427
NS_IMETHODIMP_(IMENotificationRequests)1428 NS_IMETHODIMP_(IMENotificationRequests)
1429 GeckoEditableSupport::GetIMENotificationRequests() {
1430 return IMENotificationRequests(IMENotificationRequests::NOTIFY_TEXT_CHANGE);
1431 }
1432
ShouldKeyboardDismiss(const nsAString & aInputType,const nsAString & aInputmode)1433 static bool ShouldKeyboardDismiss(const nsAString& aInputType,
1434 const nsAString& aInputmode) {
1435 // Some input type uses the prompt to input value. So it is unnecessary to
1436 // show software keyboard.
1437 return aInputmode.EqualsLiteral("none") || aInputType.EqualsLiteral("date") ||
1438 aInputType.EqualsLiteral("time") ||
1439 aInputType.EqualsLiteral("month") ||
1440 aInputType.EqualsLiteral("week") ||
1441 aInputType.EqualsLiteral("datetime-local");
1442 }
1443
SetInputContext(const InputContext & aContext,const InputContextAction & aAction)1444 void GeckoEditableSupport::SetInputContext(const InputContext& aContext,
1445 const InputContextAction& aAction) {
1446 // SetInputContext is called from chrome process only
1447 MOZ_ASSERT(XRE_IsParentProcess());
1448 MOZ_ASSERT(mEditable);
1449
1450 ALOGIME(
1451 "IME: SetInputContext: aContext=%s, "
1452 "aAction={mCause=%s, mFocusChange=%s}",
1453 ToString(aContext).c_str(), ToString(aAction.mCause).c_str(),
1454 ToString(aAction.mFocusChange).c_str());
1455
1456 mInputContext = aContext;
1457
1458 if (mInputContext.mIMEState.mEnabled != IMEEnabled::Disabled &&
1459 !ShouldKeyboardDismiss(mInputContext.mHTMLInputType,
1460 mInputContext.mHTMLInputInputmode) &&
1461 aAction.UserMightRequestOpenVKB()) {
1462 // Don't reset keyboard when we should simply open the vkb
1463 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OPEN_VKB);
1464 return;
1465 }
1466
1467 // Post an event to keep calls in order relative to NotifyIME.
1468 nsAppShell::PostEvent([this, self = RefPtr<GeckoEditableSupport>(this),
1469 context = mInputContext, action = aAction] {
1470 nsCOMPtr<nsIWidget> widget = GetWidget();
1471
1472 if (!widget || widget->Destroyed()) {
1473 return;
1474 }
1475 NotifyIMEContext(context, action);
1476 });
1477 }
1478
NotifyIMEContext(const InputContext & aContext,const InputContextAction & aAction)1479 void GeckoEditableSupport::NotifyIMEContext(const InputContext& aContext,
1480 const InputContextAction& aAction) {
1481 const bool inPrivateBrowsing = aContext.mInPrivateBrowsing;
1482 // isUserAction is used whether opening virtual keyboard. But long press
1483 // shouldn't open it.
1484 const bool isUserAction =
1485 aAction.mCause != InputContextAction::CAUSE_LONGPRESS &&
1486 !(aAction.mCause == InputContextAction::CAUSE_UNKNOWN_CHROME &&
1487 aContext.mIMEState.mEnabled == IMEEnabled::Enabled) &&
1488 (aAction.IsHandlingUserInput() || aContext.mHasHandledUserInput);
1489 const int32_t flags =
1490 (inPrivateBrowsing ? EditableListener::IME_FLAG_PRIVATE_BROWSING : 0) |
1491 (isUserAction ? EditableListener::IME_FLAG_USER_ACTION : 0) |
1492 (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED
1493 ? EditableListener::IME_FOCUS_NOT_CHANGED
1494 : 0);
1495
1496 mEditable->NotifyIMEContext(
1497 static_cast<int32_t>(aContext.mIMEState.mEnabled),
1498 aContext.mHTMLInputType, aContext.mHTMLInputInputmode,
1499 aContext.mActionHint, aContext.mAutocapitalize, flags);
1500 }
1501
GetInputContext()1502 InputContext GeckoEditableSupport::GetInputContext() {
1503 // GetInputContext is called from chrome process only
1504 MOZ_ASSERT(XRE_IsParentProcess());
1505 InputContext context = mInputContext;
1506 context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
1507 return context;
1508 }
1509
TransferParent(jni::Object::Param aEditableParent)1510 void GeckoEditableSupport::TransferParent(jni::Object::Param aEditableParent) {
1511 AutoGeckoEditableBlocker blocker(this);
1512
1513 mEditable->SetParent(aEditableParent);
1514
1515 // If we are already focused, make sure the new parent has our token
1516 // and focus information, so it can accept additional calls from us.
1517 if (mIMEFocusCount > 0) {
1518 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN);
1519 if (mIsRemote) {
1520 // GeckoEditableSupport::SetInputContext is called on chrome process
1521 // only, so mInputContext may be still invalid since it is set after
1522 // we have gotton focus.
1523 RefPtr<GeckoEditableSupport> self(this);
1524 nsAppShell::PostEvent([self = std::move(self)] {
1525 NS_WARNING_ASSERTION(
1526 self->mDispatcher,
1527 "Text dispatcher is still null. Why don't we get focus yet?");
1528 self->NotifyIMEContext(self->mInputContext, InputContextAction());
1529 });
1530 } else {
1531 NotifyIMEContext(mInputContext, InputContextAction());
1532 }
1533 mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS);
1534 // We have focus, so don't destroy editable child.
1535 return;
1536 }
1537
1538 if (mIsRemote && !mDispatcher) {
1539 // Detach now if we were only attached temporarily.
1540 OnRemovedFrom(/* dispatcher */ nullptr);
1541 }
1542 }
1543
SetOnBrowserChild(dom::BrowserChild * aBrowserChild)1544 void GeckoEditableSupport::SetOnBrowserChild(dom::BrowserChild* aBrowserChild) {
1545 MOZ_ASSERT(!XRE_IsParentProcess());
1546 NS_ENSURE_TRUE_VOID(aBrowserChild);
1547
1548 const dom::ContentChild* const contentChild =
1549 dom::ContentChild::GetSingleton();
1550 RefPtr<widget::PuppetWidget> widget(aBrowserChild->WebWidget());
1551 NS_ENSURE_TRUE_VOID(contentChild && widget);
1552
1553 // Get the content/tab ID in order to get the correct
1554 // IGeckoEditableParent object, which GeckoEditableChild uses to
1555 // communicate with the parent process.
1556 const uint64_t contentId = contentChild->GetID();
1557 const uint64_t tabId = aBrowserChild->GetTabId();
1558 NS_ENSURE_TRUE_VOID(contentId && tabId);
1559
1560 RefPtr<widget::TextEventDispatcherListener> listener =
1561 widget->GetNativeTextEventDispatcherListener();
1562
1563 if (!listener ||
1564 listener.get() ==
1565 static_cast<widget::TextEventDispatcherListener*>(widget)) {
1566 // We need to set a new listener.
1567 const auto editableChild = java::GeckoEditableChild::New(
1568 /* parent */ nullptr, /* default */ false);
1569
1570 // Temporarily attach so we can receive the initial editable parent.
1571 auto editableSupport =
1572 jni::NativeWeakPtrHolder<GeckoEditableSupport>::Attach(editableChild,
1573 editableChild);
1574 auto accEditableSupport(editableSupport.Access());
1575 MOZ_RELEASE_ASSERT(accEditableSupport);
1576
1577 // Tell PuppetWidget to use our listener for IME operations.
1578 widget->SetNativeTextEventDispatcherListener(
1579 accEditableSupport.AsRefPtr().get());
1580
1581 accEditableSupport->mEditableAttached = true;
1582
1583 // Connect the new child to a parent that corresponds to the BrowserChild.
1584 java::GeckoServiceChildProcess::GetEditableParent(editableChild, contentId,
1585 tabId);
1586 return;
1587 }
1588
1589 // We need to update the existing listener to use the new parent.
1590
1591 // We expect the existing TextEventDispatcherListener to be a
1592 // GeckoEditableSupport object, so we perform a sanity check to make
1593 // sure, by comparing their respective vtable pointers.
1594 const RefPtr<widget::GeckoEditableSupport> dummy =
1595 new widget::GeckoEditableSupport(/* child */ nullptr);
1596 NS_ENSURE_TRUE_VOID(*reinterpret_cast<const uintptr_t*>(listener.get()) ==
1597 *reinterpret_cast<const uintptr_t*>(dummy.get()));
1598
1599 const auto support =
1600 static_cast<widget::GeckoEditableSupport*>(listener.get());
1601 if (!support->mEditableAttached) {
1602 // Temporarily attach so we can receive the initial editable parent.
1603 jni::NativeWeakPtrHolder<GeckoEditableSupport>::AttachExisting(
1604 support->GetJavaEditable(), do_AddRef(support));
1605 support->mEditableAttached = true;
1606 }
1607
1608 // Transfer to a new parent that corresponds to the BrowserChild.
1609 java::GeckoServiceChildProcess::GetEditableParent(support->GetJavaEditable(),
1610 contentId, tabId);
1611 }
1612
GetWidget() const1613 nsIWidget* GeckoEditableSupport::GetWidget() const {
1614 MOZ_ASSERT(NS_IsMainThread());
1615 return mDispatcher ? mDispatcher->GetWidget() : GetNsWindow();
1616 }
1617
GetNsWindow() const1618 nsWindow* GeckoEditableSupport::GetNsWindow() const {
1619 MOZ_ASSERT(NS_IsMainThread());
1620
1621 auto acc(mWindow.Access());
1622 if (!acc) {
1623 return nullptr;
1624 }
1625
1626 return acc->GetNsWindow();
1627 }
1628
1629 } // namespace widget
1630 } // namespace mozilla
1631