/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*- * vim: set sw=2 ts=4 expandtab: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
switch (androidKeyCode) { // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3) case AKEYCODE_BACK: return NS_VK_ESCAPE; // KEYCODE_CALL (5) ... KEYCODE_POUND (18) case AKEYCODE_DPAD_UP: return NS_VK_UP; case AKEYCODE_DPAD_DOWN: return NS_VK_DOWN; case AKEYCODE_DPAD_LEFT: return NS_VK_LEFT; case AKEYCODE_DPAD_RIGHT: return NS_VK_RIGHT; case AKEYCODE_DPAD_CENTER: return NS_VK_RETURN; case AKEYCODE_VOLUME_UP: return NS_VK_VOLUME_UP; case AKEYCODE_VOLUME_DOWN: return NS_VK_VOLUME_DOWN; // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54) case AKEYCODE_COMMA: return NS_VK_COMMA; case AKEYCODE_PERIOD: return NS_VK_PERIOD; case AKEYCODE_ALT_LEFT: return NS_VK_ALT; case AKEYCODE_ALT_RIGHT: return NS_VK_ALT; case AKEYCODE_SHIFT_LEFT: return NS_VK_SHIFT; case AKEYCODE_SHIFT_RIGHT: return NS_VK_SHIFT; case AKEYCODE_TAB: return NS_VK_TAB; case AKEYCODE_SPACE: return NS_VK_SPACE; // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65) case AKEYCODE_ENTER: return NS_VK_RETURN; case AKEYCODE_DEL: return NS_VK_BACK; // Backspace case AKEYCODE_GRAVE: return NS_VK_BACK_QUOTE; // KEYCODE_MINUS (69) case AKEYCODE_EQUALS: return NS_VK_EQUALS; case AKEYCODE_LEFT_BRACKET: return NS_VK_OPEN_BRACKET; case AKEYCODE_RIGHT_BRACKET: return NS_VK_CLOSE_BRACKET; case AKEYCODE_BACKSLASH: return NS_VK_BACK_SLASH; case AKEYCODE_SEMICOLON: return NS_VK_SEMICOLON; // KEYCODE_APOSTROPHE (75) case AKEYCODE_SLASH: return NS_VK_SLASH; // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90) case AKEYCODE_MUTE: return NS_VK_VOLUME_MUTE; case AKEYCODE_PAGE_UP: return NS_VK_PAGE_UP; case AKEYCODE_PAGE_DOWN: return NS_VK_PAGE_DOWN; // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110) case AKEYCODE_ESCAPE: return NS_VK_ESCAPE; case AKEYCODE_FORWARD_DEL: return NS_VK_DELETE; case AKEYCODE_CTRL_LEFT: return NS_VK_CONTROL; case AKEYCODE_CTRL_RIGHT: return NS_VK_CONTROL; case AKEYCODE_CAPS_LOCK: return NS_VK_CAPS_LOCK; case AKEYCODE_SCROLL_LOCK: return NS_VK_SCROLL_LOCK; // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119) case AKEYCODE_SYSRQ: return NS_VK_PRINTSCREEN; case AKEYCODE_BREAK: return NS_VK_PAUSE; case AKEYCODE_MOVE_HOME: return NS_VK_HOME; case AKEYCODE_MOVE_END: return NS_VK_END; case AKEYCODE_INSERT: return NS_VK_INSERT; // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130) case AKEYCODE_F1: return NS_VK_F1; case AKEYCODE_F2: return NS_VK_F2; case AKEYCODE_F3: return NS_VK_F3; case AKEYCODE_F4: return NS_VK_F4; case AKEYCODE_F5: return NS_VK_F5; case AKEYCODE_F6: return NS_VK_F6; case AKEYCODE_F7: return NS_VK_F7; case AKEYCODE_F8: return NS_VK_F8; case AKEYCODE_F9: return NS_VK_F9; case AKEYCODE_F10: return NS_VK_F10; case AKEYCODE_F11: return NS_VK_F11; case AKEYCODE_F12: return NS_VK_F12; case AKEYCODE_NUM_LOCK: return NS_VK_NUM_LOCK; case AKEYCODE_NUMPAD_0: return NS_VK_NUMPAD0; case AKEYCODE_NUMPAD_1: return NS_VK_NUMPAD1; case AKEYCODE_NUMPAD_2: return NS_VK_NUMPAD2; case AKEYCODE_NUMPAD_3: return NS_VK_NUMPAD3; case AKEYCODE_NUMPAD_4: return NS_VK_NUMPAD4; case AKEYCODE_NUMPAD_5: return NS_VK_NUMPAD5; case AKEYCODE_NUMPAD_6: return NS_VK_NUMPAD6; case AKEYCODE_NUMPAD_7: return NS_VK_NUMPAD7; case AKEYCODE_NUMPAD_8: return NS_VK_NUMPAD8; case AKEYCODE_NUMPAD_9: return NS_VK_NUMPAD9; case AKEYCODE_NUMPAD_DIVIDE: return NS_VK_DIVIDE; case AKEYCODE_NUMPAD_MULTIPLY: return NS_VK_MULTIPLY; case AKEYCODE_NUMPAD_SUBTRACT: return NS_VK_SUBTRACT; case AKEYCODE_NUMPAD_ADD: return NS_VK_ADD; case AKEYCODE_NUMPAD_DOT: return NS_VK_DECIMAL; case AKEYCODE_NUMPAD_COMMA: return NS_VK_SEPARATOR; case AKEYCODE_NUMPAD_ENTER: return NS_VK_RETURN; case AKEYCODE_NUMPAD_EQUALS: return NS_VK_EQUALS; // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210)
// Needs to confirm the behavior. If the key switches the open state // of Japanese IME (or switches input character between Hiragana and // Roman numeric characters), then, it might be better to use // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows. case AKEYCODE_ZENKAKU_HANKAKU: return 0; case AKEYCODE_EISU: return NS_VK_EISU; case AKEYCODE_MUHENKAN: return NS_VK_NONCONVERT; case AKEYCODE_HENKAN: return NS_VK_CONVERT; case AKEYCODE_KATAKANA_HIRAGANA: return 0; case AKEYCODE_YEN: return NS_VK_BACK_SLASH; // Same as other platforms. case AKEYCODE_RO: return NS_VK_BACK_SLASH; // Same as other platforms. case AKEYCODE_KANA: return NS_VK_KANA; case AKEYCODE_ASSIST: return NS_VK_HELP;
// the A key is the action key for gamepad devices. case AKEYCODE_BUTTON_A: return NS_VK_RETURN;
default:
ALOG( "ConvertAndroidKeyCodeToDOMKeyCode: " "No DOM keycode for Android keycode %d", int(androidKeyCode)); return 0;
}
}
static KeyNameIndex ConvertAndroidKeyCodeToKeyNameIndex(
int32_t keyCode, int32_t action, int32_t domPrintableKeyValue) { // Special-case alphanumeric keycodes because they are most common. if (keyCode >= AKEYCODE_A && keyCode <= AKEYCODE_Z) { return KEY_NAME_INDEX_USE_STRING;
}
// KEYCODE_0 (7) ... KEYCODE_9 (16) case AKEYCODE_STAR: // '*' key case AKEYCODE_POUND: // '#' key
// KEYCODE_A (29) ... KEYCODE_Z (54)
case AKEYCODE_COMMA: // ',' key case AKEYCODE_PERIOD: // '.' key case AKEYCODE_SPACE: case AKEYCODE_GRAVE: // '`' key case AKEYCODE_MINUS: // '-' key case AKEYCODE_EQUALS: // '=' key case AKEYCODE_LEFT_BRACKET: // '[' key case AKEYCODE_RIGHT_BRACKET: // ']' key case AKEYCODE_BACKSLASH: // '\' key case AKEYCODE_SEMICOLON: // ';' key case AKEYCODE_APOSTROPHE: // ''' key case AKEYCODE_SLASH: // '/' key case AKEYCODE_AT: // '@' key case AKEYCODE_PLUS: // '+' key
case AKEYCODE_NUMPAD_0: case AKEYCODE_NUMPAD_1: case AKEYCODE_NUMPAD_2: case AKEYCODE_NUMPAD_3: case AKEYCODE_NUMPAD_4: case AKEYCODE_NUMPAD_5: case AKEYCODE_NUMPAD_6: case AKEYCODE_NUMPAD_7: case AKEYCODE_NUMPAD_8: case AKEYCODE_NUMPAD_9: case AKEYCODE_NUMPAD_DIVIDE: case AKEYCODE_NUMPAD_MULTIPLY: case AKEYCODE_NUMPAD_SUBTRACT: case AKEYCODE_NUMPAD_ADD: case AKEYCODE_NUMPAD_DOT: case AKEYCODE_NUMPAD_COMMA: case AKEYCODE_NUMPAD_EQUALS: case AKEYCODE_NUMPAD_LEFT_PAREN: case AKEYCODE_NUMPAD_RIGHT_PAREN:
case AKEYCODE_YEN: // yen sign key case AKEYCODE_RO: // Japanese Ro key return KEY_NAME_INDEX_USE_STRING;
case AKEYCODE_NUM: // XXX Not sure case AKEYCODE_PICTSYMBOLS:
case AKEYCODE_BUTTON_A: case AKEYCODE_BUTTON_B: case AKEYCODE_BUTTON_C: case AKEYCODE_BUTTON_X: case AKEYCODE_BUTTON_Y: case AKEYCODE_BUTTON_Z: case AKEYCODE_BUTTON_L1: case AKEYCODE_BUTTON_R1: case AKEYCODE_BUTTON_L2: case AKEYCODE_BUTTON_R2: case AKEYCODE_BUTTON_THUMBL: case AKEYCODE_BUTTON_THUMBR: case AKEYCODE_BUTTON_START: case AKEYCODE_BUTTON_SELECT: case AKEYCODE_BUTTON_MODE:
case AKEYCODE_MEDIA_CLOSE:
case AKEYCODE_BUTTON_1: case AKEYCODE_BUTTON_2: case AKEYCODE_BUTTON_3: case AKEYCODE_BUTTON_4: case AKEYCODE_BUTTON_5: case AKEYCODE_BUTTON_6: case AKEYCODE_BUTTON_7: case AKEYCODE_BUTTON_8: case AKEYCODE_BUTTON_9: case AKEYCODE_BUTTON_10: case AKEYCODE_BUTTON_11: case AKEYCODE_BUTTON_12: case AKEYCODE_BUTTON_13: case AKEYCODE_BUTTON_14: case AKEYCODE_BUTTON_15: case AKEYCODE_BUTTON_16: return KEY_NAME_INDEX_Unidentified;
case AKEYCODE_UNKNOWN:
MOZ_ASSERT(action != AKEY_EVENT_ACTION_MULTIPLE, "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!"); // It's actually an unknown key if the action isn't ACTION_MULTIPLE. // However, it might cause text input. So, let's check the value. return domPrintableKeyValue ? KEY_NAME_INDEX_USE_STRING
: KEY_NAME_INDEX_Unidentified;
default:
ALOG( "ConvertAndroidKeyCodeToKeyNameIndex: " "No DOM key name index for Android keycode %d",
keyCode); return KEY_NAME_INDEX_Unidentified;
}
}
// This is the blocker helper class whether disposing GeckoEditableChild now. // During JNI call from GeckoEditableChild, we shouldn't dispose it. class MOZ_RAII AutoGeckoEditableBlocker final { public: explicit AutoGeckoEditableBlocker(GeckoEditableSupport* aGeckoEditableSupport)
: mGeckoEditable(aGeckoEditableSupport) {
mGeckoEditable->AddBlocker();
}
~AutoGeckoEditableBlocker() { mGeckoEditable->ReleaseBlocker(); }
if (!aIsSynthesizedImeKey) { if (nsWindow* window = GetNsWindow()) {
window->UserActivity();
}
} elseif (aIsSynthesizedImeKey && mIMEMaskEventsCount > 0) { // Don't synthesize editor keys when not focused. return;
}
EventMessage msg; if (aAction == java::sdk::KeyEvent::ACTION_DOWN) {
msg = eKeyDown;
} elseif (aAction == java::sdk::KeyEvent::ACTION_UP) {
msg = eKeyUp;
} elseif (aAction == java::sdk::KeyEvent::ACTION_MULTIPLE) { // Keys with multiple action are handled in Java, // and we should never see one here
MOZ_CRASH("Cannot handle key with multiple action");
} else {
NS_WARNING("Unknown key action event"); return;
}
if (nsIWidget::UsePuppetWidgets()) { // Don't use native key bindings.
event.PreventNativeKeyBindings();
}
if (aIsSynthesizedImeKey) { // Keys synthesized by Java IME code are saved in the mIMEKeyEvents // array until the next IME_REPLACE_TEXT event, at which point // these keys are dispatched in sequence.
mIMEKeyEvents.AppendElement(UniquePtr<WidgetEvent>(event.Duplicate()));
} else {
NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(dispatcher));
dispatcher->DispatchKeyboardEvent(msg, event, status); if (widget->Destroyed() || status == nsEventStatus_eConsumeNoDefault) { // Skip default processing. return;
}
mEditable->OnDefaultKeyEvent(aOriginalEvent);
}
// Only send keypress after keydown. if (msg != eKeyDown) { return;
}
/* * Send dummy key events for pages that are unaware of input events, * to provide web compatibility for pages that depend on key events.
*/ void GeckoEditableSupport::SendIMEDummyKeyEvent(nsIWidget* aWidget,
EventMessage msg) {
AutoGeckoEditableBlocker blocker(this);
nsEventStatus status = nsEventStatus_eIgnore;
MOZ_ASSERT(mDispatcher);
WidgetKeyboardEvent event(true, msg, aWidget); // TODO: If we can know scan code of the key event which caused replacing // composition string, we should set mCodeNameIndex here. Then, // we should rename this method because it becomes not a "dummy" // keyboard event.
event.mKeyCode = NS_VK_PROCESSKEY;
event.mKeyNameIndex = KEY_NAME_INDEX_Process; // KeyboardEvents marked as "processed by IME" shouldn't cause any edit // actions. So, we should set their native key binding to none before // dispatch to avoid crash on PuppetWidget and avoid running redundant // path to look for native key bindings. if (nsIWidget::UsePuppetWidgets()) {
event.PreventNativeKeyBindings();
}
NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(mDispatcher));
mDispatcher->DispatchKeyboardEvent(msg, event, status);
}
void GeckoEditableSupport::FlushIMEChanges(FlushChangesFlag aFlags) { // Only send change notifications if we are *not* masking events, // i.e. if we have a focused editor,
NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount);
if (mIMEDelaySynchronizeReply && mIMEActiveCompositionCount > 0) { // We are still expecting more composition events to be handled. Once // that happens, FlushIMEChanges will be called again. return;
}
auto shouldAbort = [=](bool aForce) -> bool { if (!aForce && !mIMETextChangedDuringFlush) { returnfalse;
} // A query event could have triggered more text changes to come in, as // indicated by our flag. If that happens, try flushing IME changes // again. if (aFlags == FLUSH_FLAG_NONE) {
FlushIMEChanges(FLUSH_FLAG_RETRY);
} else { // Don't retry if already retrying, to avoid infinite loops.
__android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport", "Already retrying IME flush");
} returntrue;
};
if (mIMESelectionChanged) { if (mCachedSelection.IsValid()) {
selStart = mCachedSelection.mStartOffset;
selEnd = mCachedSelection.mEndOffset;
} else { // XXX Unfortunately we don't know current selection via selection // change notification. // eQuerySelectedText might be newer data than text change data. // It means that GeckoEditableChild.onSelectionChange may throw // IllegalArgumentException since we don't merge with newer text // change.
WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
widget);
widget->DispatchEvent(&querySelectedTextEvent, status);
if (shouldAbort(
NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection()))) { return;
}
if (aFlags == FLUSH_FLAG_RECOVER && textTransaction.IsValid()) { // Sometimes we get out-of-bounds selection during recovery. // Limit the offsets so we don't crash. const int32_t end = textTransaction.start + textTransaction.text.Length();
selStart = std::min(selStart, end);
selEnd = std::min(selEnd, end);
}
}
JNIEnv* const env = jni::GetGeckoThreadEnv(); auto flushOnException = [=]() -> bool { if (!env->ExceptionCheck()) { returnfalse;
} if (aFlags != FLUSH_FLAG_RECOVER) { // First time seeing an exception; try flushing text.
env->ExceptionClear();
__android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport", "Recovering from IME exception");
FlushIMEText(FLUSH_FLAG_RECOVER);
} else { // Give up because we've already tried. #ifdef RELEASE_OR_BETA
env->ExceptionClear(); #else
MOZ_CATCH_JNI_EXCEPTION(env); #endif
} returntrue;
};
// Commit the text change and selection change transaction.
mIMEPendingTextChange.Clear();
if (textTransaction.IsValid()) {
mEditable->OnTextChange(textTransaction.text, textTransaction.start,
textTransaction.oldEnd, textTransaction.newEnd,
causedOnlyByComposition); if (flushOnException()) { return;
}
}
if (mIMESelectionChanged) {
mIMESelectionChanged = false; if (mDispatcher) { // mCausedOnlyByComposition may be true on committing text. // So even if true, there is no composition.
causedOnlyByComposition &= mDispatcher->IsComposing();
}
mEditable->OnSelectionChange(selStart, selEnd, causedOnlyByComposition);
flushOnException();
}
}
void GeckoEditableSupport::FlushIMEText(FlushChangesFlag aFlags) {
NS_WARNING_ASSERTION(
!mIMEDelaySynchronizeReply || !mIMEActiveCompositionCount, "Cannot synchronize Java text with Gecko text");
// Notify Java of the newly focused content
mIMEPendingTextChange.Clear();
mIMESelectionChanged = true;
// Use 'INT32_MAX / 2' here because subsequent text changes might combine // with this text change, and overflow might occur if we just use // INT32_MAX.
IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
notification.mTextChangeData.mStartOffset = 0;
notification.mTextChangeData.mRemovedEndOffset = INT32_MAX / 2;
notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
NotifyIME(mDispatcher, notification);
if (mIMEDelaySynchronizeReply) { // If we are waiting for other events to reply, // queue this reply as well.
mIMEActiveSynchronizeCount++; return;
} if (!mIMEMaskEventsCount) {
FlushIMEChanges();
}
mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT);
}
// Return true if processed and we should reply to the OnImeReplaceText // event later. Return false if _not_ processed and we should reply to the // OnImeReplaceText event now.
if (mIMEMaskEventsCount > 0) { // Not focused; still reply to events, but don't do anything else. returnfalse;
}
if (nsWindow* window = GetNsWindow()) {
window->UserActivity();
}
/* Replace text in Gecko thread from aStart to aEnd with the string text.
*/
nsCOMPtr<nsIWidget> widget = GetWidget();
NS_ENSURE_TRUE(mDispatcher && widget, false);
NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false);
nsString string(aText->ToString()); constbool composing = !mIMERanges->IsEmpty();
nsEventStatus status = nsEventStatus_eIgnore; bool textChanged = composing; // Whether deleting content before setting or committing composition text. bool performDeletion = false; // Dispatch composition start to set current composition. bool needDispatchCompositionStart = false;
if (!mIMEKeyEvents.IsEmpty() || !composition || !mDispatcher->IsComposing() ||
uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
composition->String().Length()) { // Only start a new composition if we have key events, // if we don't have an existing composition, or // the replaced text does not match our composition.
textChanged |= RemoveComposition();
#ifdef NIGHTLY_BUILD
{
nsEventStatus status = nsEventStatus_eIgnore;
WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
widget);
widget->DispatchEvent(&querySelectedTextEvent, status); if (querySelectedTextEvent.Succeeded()) {
ALOGIME( "IME: Current selection: %s",
ToString(querySelectedTextEvent.mReply->mOffsetAndData).c_str());
}
} #endif
// If aStart or aEnd is negative value, we use current selection instead // of updating the selection. if (aStart >= 0 && aEnd >= 0) { // Use text selection to set target position(s) for // insert, or replace, of text.
WidgetSelectionEvent event(true, eSetSelection, widget);
event.mOffset = uint32_t(aStart);
event.mLength = uint32_t(aEnd - aStart);
event.mExpandToClusterBoundary = false;
event.mReason = nsISelectionListener::IME_REASON;
widget->DispatchEvent(&event, status);
}
if (!mIMEKeyEvents.IsEmpty()) { bool ignoreNextKeyPress = false; for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) { constauto event = mIMEKeyEvents[i]->AsKeyboardEvent(); // widget for duplicated events is initially nullptr.
event->mWidget = widget;
status = nsEventStatus_eIgnore; if (event->mMessage != eKeyPress) {
mDispatcher->DispatchKeyboardEvent(event->mMessage, *event, status); // Skip default processing. It means that next key press shouldn't // be dispatched.
ignoreNextKeyPress = event->mMessage == eKeyDown &&
status == nsEventStatus_eConsumeNoDefault;
} else { if (ignoreNextKeyPress) { // Don't dispatch key press since previous key down is consumed.
ignoreNextKeyPress = false; continue;
}
mDispatcher->MaybeDispatchKeypressEvents(*event, status); if (status == nsEventStatus_eConsumeNoDefault) {
textChanged = true;
}
} if (!mDispatcher || widget->Destroyed()) { // Don't wait for any text change event.
textChanged = false; break;
}
}
mIMEKeyEvents.Clear(); return textChanged;
}
if (aStart != aEnd) { if (composing) { // Actually Gecko doesn't start composition, so it is unnecessary to // delete content before setting composition string.
needDispatchCompositionStart = true;
} else { // Perform a deletion first.
performDeletion = true;
}
}
} elseif (composition->String().Equals(string)) { // If the new text is the same as the existing composition text, // the NS_COMPOSITION_CHANGE event does not generate a text // change notification. However, the Java side still expects // one, so we manually generate a notification. // // Also, since this is IME change, we have to set mCausedOnlyByComposition.
IMENotification::TextChangeData dummyChange(aStart, aEnd, aEnd, true, false);
PostFlushIMEChanges();
mIMESelectionChanged = true;
AddIMETextChange(dummyChange);
textChanged = true;
}
SendIMEDummyKeyEvent(widget, eKeyDown); if (!mDispatcher || widget->Destroyed()) { returnfalse;
}
if (!StaticPrefs::intl_ime_use_composition_events_for_insert_text() &&
!composing && !mDispatcher->IsComposing() && aStart == aEnd &&
!string.IsEmpty()) { // We don't start composition yet and inserting text has no composition. // So we can simply insert text without composition.
ALOGIME("IME: Don't use composition event to insert text");
WidgetContentCommandEvent insertTextEvent(true, eContentCommandInsertText,
widget);
insertTextEvent.mString = Some(string);
widget->DispatchEvent(&insertTextEvent, status); if (!mDispatcher || widget->Destroyed()) { returnfalse;
}
SendIMEDummyKeyEvent(widget, eKeyUp); returntrue;
}
if (needDispatchCompositionStart) { // StartComposition sets composition string from selected string.
nsEventStatus status = nsEventStatus_eIgnore;
mDispatcher->StartComposition(status); if (!mDispatcher || widget->Destroyed()) { returnfalse;
}
} elseif (performDeletion) {
WidgetContentCommandEvent event(true, eContentCommandDelete, widget);
widget->DispatchEvent(&event, status); if (!mDispatcher || widget->Destroyed()) { returnfalse;
}
textChanged = true;
}
if (composing) {
mDispatcher->SetPendingComposition(string, mIMERanges);
mDispatcher->FlushPendingComposition(status);
mIMEActiveCompositionCount++; // Ensure IME ranges are empty.
mIMERanges->Clear();
} elseif (!string.IsEmpty() || mDispatcher->IsComposing()) {
mDispatcher->CommitComposition(status, &string);
mIMEActiveCompositionCount++;
textChanged = true;
} if (!mDispatcher || widget->Destroyed()) { returnfalse;
}
SendIMEDummyKeyEvent(widget, eKeyUp); // Widget may be destroyed after dispatching the above event.
// A composition with no ranges means we want to set the selection. if (mIMERanges->IsEmpty()) { if (keepCurrent && mDispatcher->IsComposing()) { // Don't set selection if we want to keep current composition. returnfalse;
}
/** * Update the composition from aStart to aEnd using information from added * ranges. This is only used for visual indication and does not affect the * text content. Only the offsets are specified and not the text content * to eliminate the possibility of this event altering the text content * unintentionally.
*/
nsString string;
RefPtr<TextComposition> composition(GetComposition());
MOZ_ASSERT(!composition || !composition->EditorIsHandlingLatestChange());
if (!composition || !mDispatcher->IsComposing() ||
uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
composition->String().Length()) { if (keepCurrent) { // Don't start a new composition if we want to keep the current one.
mIMERanges->Clear(); returnfalse;
}
// Only start new composition if we don't have an existing one, // or if the existing composition doesn't match the new one.
RemoveComposition();
// Post an event because we have to flush the text before sending a // focus event, and we may not be able to flush text during the // NotifyIME call.
nsAppShell::PostEvent([this, self, dispatcher] {
nsCOMPtr<nsIWidget> widget = dispatcher->GetWidget();
--mIMEMaskEventsCount; if (!mIMEFocusCount || !widget || widget->Destroyed()) { return;
}
if (mIsRemote) { if (!mEditableAttached) { // Re-attach on focus; see OnRemovedFrom().
jni::NativeWeakPtrHolder<GeckoEditableSupport>::AttachExisting(
mEditable, do_AddRef(this));
mEditableAttached = true;
} // Because GeckoEditableSupport in content process doesn't // manage the active input context, we need to retrieve the // input context from the widget, for use by // OnImeReplaceText.
mInputContext = widget->GetInputContext();
}
mDispatcher = dispatcher;
mIMEKeyEvents.Clear();
case NOTIFY_IME_OF_TEXT_CHANGE: {
ALOGIME("IME: NOTIFY_IME_OF_TEXT_CHANGE: TextChangeData=%s",
ToString(aNotification.mTextChangeData).c_str());
/* Make sure Java's selection is up-to-date */
PostFlushIMEChanges();
mIMESelectionChanged = true;
AddIMETextChange(aNotification.mTextChangeData); break;
}
case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
// NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED isn't sent per IME call. // Receiving this event means that Gecko has already handled all IME // composing events in queue. // if (mIsRemote) {
OnNotifyIMEOfCompositionEventHandled();
} else { // Also, when receiving this event, mIMEDelaySynchronizeReply won't // update yet on non-e10s case since IME event is posted before updating // it. So we have to delay handling of this event.
RefPtr<GeckoEditableSupport> self(this);
nsAppShell::PostEvent(
[this, self] { OnNotifyIMEOfCompositionEventHandled(); });
} break;
}
default: break;
} return NS_OK;
}
void GeckoEditableSupport::OnNotifyIMEOfCompositionEventHandled() { // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED may be merged with multiple events, // so reset count.
mIMEActiveCompositionCount = 0; if (mIMEDelaySynchronizeReply) {
FlushIMEChanges();
}
// Hardware keyboard support requires each string rect. if (mIMEMonitorCursor) {
UpdateCompositionRects();
}
}
staticbool ShouldKeyboardDismiss(const nsAString& aInputType, const nsAString& aInputMode) { // Some input type uses the prompt to input value. So it is unnecessary to // show software keyboard. return aInputMode.EqualsLiteral("none") || aInputType.EqualsLiteral("date") ||
aInputType.EqualsLiteral("time") ||
aInputType.EqualsLiteral("month") ||
aInputType.EqualsLiteral("week") ||
aInputType.EqualsLiteral("datetime-local");
}
void GeckoEditableSupport::SetInputContext(const InputContext& aContext, const InputContextAction& aAction) { // SetInputContext is called from chrome process only
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(mEditable);
if (mInputContext.mIMEState.mEnabled != IMEEnabled::Disabled &&
!ShouldKeyboardDismiss(mInputContext.mHTMLInputType,
mInputContext.mHTMLInputMode) &&
aAction.UserMightRequestOpenVKB()) { // Don't reset keyboard when we should simply open the vkb
mEditable->NotifyIME(EditableListener::NOTIFY_IME_OPEN_VKB); return;
}
// Post an event to keep calls in order relative to NotifyIME.
nsAppShell::PostEvent([this, self = RefPtr<GeckoEditableSupport>(this),
context = mInputContext, action = aAction] {
nsCOMPtr<nsIWidget> widget = GetWidget();
InputContext GeckoEditableSupport::GetInputContext() { // GetInputContext is called from chrome process only
MOZ_ASSERT(XRE_IsParentProcess());
InputContext context = mInputContext;
context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; return context;
}
// If we are already focused, make sure the new parent has our token // and focus information, so it can accept additional calls from us. if (mIMEFocusCount > 0) {
mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN); if (mIsRemote) { // GeckoEditableSupport::SetInputContext is called on chrome process // only, so mInputContext may be still invalid since it is set after // we have gotton focus.
RefPtr<GeckoEditableSupport> self(this);
nsAppShell::PostEvent([self = std::move(self)] {
NS_WARNING_ASSERTION(
self->mDispatcher, "Text dispatcher is still null. Why don't we get focus yet?");
self->NotifyIMEContext(self->mInputContext, InputContextAction());
});
} else {
NotifyIMEContext(mInputContext, InputContextAction());
}
mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS); // We have focus, so don't destroy editable child. return;
}
if (mIsRemote && !mDispatcher) { // Detach now if we were only attached temporarily.
OnRemovedFrom(/* dispatcher */ nullptr);
}
}
// Get the content/tab ID in order to get the correct // IGeckoEditableParent object, which GeckoEditableChild uses to // communicate with the parent process. const uint64_t contentId = contentChild->GetID(); const uint64_t tabId = aBrowserChild->GetTabId();
NS_ENSURE_TRUE_VOID(contentId && tabId);
if (!listener ||
listener.get() == static_cast<widget::TextEventDispatcherListener*>(widget)) { // We need to set a new listener. constauto editableChild = java::GeckoEditableChild::New( /* parent */ nullptr, /* default */ false);
// Temporarily attach so we can receive the initial editable parent. auto editableSupport =
jni::NativeWeakPtrHolder<GeckoEditableSupport>::Attach(editableChild,
editableChild); auto accEditableSupport(editableSupport.Access());
MOZ_RELEASE_ASSERT(accEditableSupport);
// Tell PuppetWidget to use our listener for IME operations.
widget->SetNativeTextEventDispatcherListener(
accEditableSupport.AsRefPtr().get());
accEditableSupport->mEditableAttached = true;
// Connect the new child to a parent that corresponds to the BrowserChild.
java::GeckoServiceChildProcess::GetEditableParent(editableChild, contentId,
tabId); return;
}
// We need to update the existing listener to use the new parent.
// We expect the existing TextEventDispatcherListener to be a // GeckoEditableSupport object, so we perform a sanity check to make // sure, by comparing their respective vtable pointers. const RefPtr<widget::GeckoEditableSupport> dummy = new widget::GeckoEditableSupport(/* child */ nullptr);
NS_ENSURE_TRUE_VOID(*reinterpret_cast<const uintptr_t*>(listener.get()) ==
*reinterpret_cast<const uintptr_t*>(dummy.get()));
constauto support = static_cast<widget::GeckoEditableSupport*>(listener.get()); if (!support->mEditableAttached) { // Temporarily attach so we can receive the initial editable parent.
jni::NativeWeakPtrHolder<GeckoEditableSupport>::AttachExisting(
support->GetJavaEditable(), do_AddRef(support));
support->mEditableAttached = true;
}
// Transfer to a new parent that corresponds to the BrowserChild.
java::GeckoServiceChildProcess::GetEditableParent(support->GetJavaEditable(),
contentId, tabId);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.