/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */
class TextInputProcessorNotification final
: public nsITextInputProcessorNotification { using SelectionChangeData = IMENotification::SelectionChangeData; using SelectionChangeDataBase = IMENotification::SelectionChangeDataBase; using TextChangeData = IMENotification::TextChangeData; using TextChangeDataBase = IMENotification::TextChangeDataBase;
TextInputProcessor::~TextInputProcessor() { if (mDispatcher && mDispatcher->IsComposing()) { // If this is composing and not canceling the composition, nobody can steal // the rights of TextEventDispatcher from this instance. Therefore, this // needs to cancel the composition here. if (NS_SUCCEEDED(IsValidStateForComposition())) {
RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
nsEventStatus status = nsEventStatus_eIgnore;
kungFuDeathGrip->CommitComposition(status, &EmptyString());
}
}
}
RefPtr<TextEventDispatcher> dispatcher = widget->GetTextEventDispatcher();
MOZ_RELEASE_ASSERT(dispatcher, "TextEventDispatcher must not be null");
// If the instance was initialized and is being initialized for same // dispatcher and same purpose, we don't need to initialize the dispatcher // again. if (mDispatcher && dispatcher == mDispatcher && aCallback == mCallback &&
aForTests == mForTests) {
aSucceeded = true; return NS_OK;
}
// If this instance is composing or dispatching an event, don't allow to // initialize again. Especially, if we allow to begin input transaction with // another TextEventDispatcher during dispatching an event, it may cause that // nobody cannot begin input transaction with it if the last event causes // opening modal dialog. if (mDispatcher &&
(mDispatcher->IsComposing() || mDispatcher->IsDispatchingEvent())) { return NS_ERROR_ALREADY_INITIALIZED;
}
// And also if another instance is composing with the new dispatcher or // dispatching an event, it'll fail to steal its ownership. Then, we should // not throw an exception, just return false. if (dispatcher->IsComposing() || dispatcher->IsDispatchingEvent()) { return NS_OK;
}
// This instance has finished preparing to link to the dispatcher. Therefore, // let's forget the old dispatcher and purpose. if (mDispatcher) {
mDispatcher->EndInputTransaction(this); if (NS_WARN_IF(mDispatcher)) { // Forcibly initialize the members if we failed to end the input // transaction.
UnlinkFromTextEventDispatcher();
}
}
// If the mMessage is eKeyUp, the caller doesn't want TIP to dispatch // eKeyDown event. if (aKeyboardEvent->mMessage == eKeyUp) { return result;
}
// Modifier keys are not allowed because managing modifier state in this // method makes this messy. if (NS_WARN_IF(aKeyboardEvent->IsModifierKeyEvent())) {
result.mResult = NS_ERROR_INVALID_ARG;
result.mCanContinue = false; return result;
}
// If the mMessage is eKeyDown, the caller doesn't want TIP to dispatch // eKeyUp event. if (aKeyboardEvent->mMessage == eKeyDown) { return result;
}
// If the widget has been destroyed, we can do nothing here.
result.mResult = IsValidStateForComposition(); if (NS_FAILED(result.mResult)) {
result.mCanContinue = false; return result;
}
NS_IMETHODIMP
TextInputProcessor::FlushPendingComposition(Event* aDOMKeyEvent,
uint32_t aKeyFlags,
uint8_t aOptionalArgc, bool* aSucceeded) {
MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
// Even if this doesn't flush pending composition actually, we need to reset // pending composition for starting next composition with new user input.
AutoPendingCompositionResetter resetter(this);
// Even if the preceding keydown event was consumed, if the composition // was already started, we shouldn't prevent the change of composition. if (dispatcherResult.mDoDefault || wasComposing) { // Preceding keydown event may cause destroying the widget. if (NS_FAILED(IsValidStateForComposition())) { return NS_OK;
}
nsEventStatus status = nsEventStatus_eIgnore;
rv = kungFuDeathGrip->FlushPendingComposition(status);
*aSucceeded = status != nsEventStatus_eConsumeNoDefault;
}
// Even if the preceding keydown event was consumed, if the composition // was already started, we shouldn't prevent the commit of composition.
nsresult rv = NS_OK; if (dispatcherResult.mDoDefault || wasComposing) { // Preceding keydown event may cause destroying the widget. if (NS_FAILED(IsValidStateForComposition())) { return NS_OK;
}
nsEventStatus status = nsEventStatus_eIgnore;
rv = kungFuDeathGrip->CommitComposition(status, aCommitString); if (aSucceeded) {
*aSucceeded = status != nsEventStatus_eConsumeNoDefault;
}
}
if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
} return NS_OK;
}
NS_IMETHODIMP
TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher, const IMENotification& aNotification) { // If This is called while this is being initialized, ignore the call. // In such case, this method should return NS_ERROR_NOT_IMPLEMENTED because // we can say, TextInputProcessor doesn't implement any handlers of the // requests and notifications. if (!mDispatcher) { return NS_ERROR_NOT_IMPLEMENTED;
}
MOZ_ASSERT(aTextEventDispatcher == mDispatcher, "Wrong TextEventDispatcher notifies this");
NS_ASSERTION(mForTests || mCallback, "mCallback can be null only when IME is initialized for tests"); if (mCallback) {
RefPtr<TextInputProcessorNotification> notification; switch (aNotification.mMessage) { case REQUEST_TO_COMMIT_COMPOSITION: {
NS_ASSERTION(aTextEventDispatcher->IsComposing(), "Why is this requested without composition?");
notification = new TextInputProcessorNotification("request-to-commit"); break;
} case REQUEST_TO_CANCEL_COMPOSITION: {
NS_ASSERTION(aTextEventDispatcher->IsComposing(), "Why is this requested without composition?");
notification = new TextInputProcessorNotification("request-to-cancel"); break;
} case NOTIFY_IME_OF_FOCUS:
notification = new TextInputProcessorNotification("notify-focus"); break; case NOTIFY_IME_OF_BLUR:
notification = new TextInputProcessorNotification("notify-blur"); break; case NOTIFY_IME_OF_TEXT_CHANGE:
notification = new TextInputProcessorNotification(aNotification.mTextChangeData); break; case NOTIFY_IME_OF_SELECTION_CHANGE:
notification = new TextInputProcessorNotification(
aNotification.mSelectionChangeData); break; case NOTIFY_IME_OF_POSITION_CHANGE:
notification = new TextInputProcessorNotification("notify-position-change"); break; default: return NS_ERROR_NOT_IMPLEMENTED;
}
MOZ_RELEASE_ASSERT(notification); bool result = false;
nsresult rv = mCallback->OnNotify(this, notification, &result); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
} return result ? NS_OK : NS_ERROR_FAILURE;
}
switch (aNotification.mMessage) { case REQUEST_TO_COMMIT_COMPOSITION: {
NS_ASSERTION(aTextEventDispatcher->IsComposing(), "Why is this requested without composition?");
CommitCompositionInternal(); return NS_OK;
} case REQUEST_TO_CANCEL_COMPOSITION: {
NS_ASSERTION(aTextEventDispatcher->IsComposing(), "Why is this requested without composition?");
CancelCompositionInternal(); return NS_OK;
} default: return NS_ERROR_NOT_IMPLEMENTED;
}
}
NS_IMETHODIMP_(IMENotificationRequests)
TextInputProcessor::GetIMENotificationRequests() { // TextInputProcessor should support all change notifications. return IMENotificationRequests(
IMENotificationRequests::NOTIFY_TEXT_CHANGE |
IMENotificationRequests::NOTIFY_POSITION_CHANGE);
}
NS_IMETHODIMP_(void)
TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) { // If This is called while this is being initialized, ignore the call. if (!mDispatcher) { return;
}
MOZ_ASSERT(aTextEventDispatcher == mDispatcher, "Wrong TextEventDispatcher notifies this");
UnlinkFromTextEventDispatcher();
}
NS_IMETHODIMP_(void)
TextInputProcessor::WillDispatchKeyboardEvent(
TextEventDispatcher* aTextEventDispatcher,
WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress, void* aData) { // TextInputProcessor doesn't set alternative char code nor modify charCode // even when Ctrl key is pressed.
}
nsresult TextInputProcessor::PrepareKeyboardEventToDispatch(
WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags) { if (NS_WARN_IF(aKeyboardEvent.mCodeNameIndex == CODE_NAME_INDEX_USE_STRING)) { return NS_ERROR_INVALID_ARG;
} if ((aKeyFlags & KEY_NON_PRINTABLE_KEY) &&
NS_WARN_IF(aKeyboardEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING)) { return NS_ERROR_INVALID_ARG;
} if ((aKeyFlags & KEY_FORCE_PRINTABLE_KEY) &&
aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
aKeyboardEvent.GetDOMKeyName(aKeyboardEvent.mKeyValue);
aKeyboardEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
} if (aKeyFlags & KEY_KEEP_KEY_LOCATION_STANDARD) { // If .location is initialized with specific value, using // KEY_KEEP_KEY_LOCATION_STANDARD must be a bug of the caller. // Let's throw an exception for notifying the developer of this bug. if (NS_WARN_IF(aKeyboardEvent.mLocation)) { return NS_ERROR_INVALID_ARG;
}
} elseif (!aKeyboardEvent.mLocation) { // If KeyboardEvent.mLocation is 0, it may be uninitialized. If so, we // should compute proper mLocation value from its .code value.
aKeyboardEvent.mLocation =
WidgetKeyboardEvent::ComputeLocationFromCodeValue(
aKeyboardEvent.mCodeNameIndex);
}
if (aKeyFlags & KEY_KEEP_KEYCODE_ZERO) { // If .keyCode is initialized with specific value, using // KEY_KEEP_KEYCODE_ZERO must be a bug of the caller. Let's throw an // exception for notifying the developer of such bug. if (NS_WARN_IF(aKeyboardEvent.mKeyCode)) { return NS_ERROR_INVALID_ARG;
}
} elseif (!aKeyboardEvent.mKeyCode &&
aKeyboardEvent.mKeyNameIndex > KEY_NAME_INDEX_Unidentified &&
aKeyboardEvent.mKeyNameIndex < KEY_NAME_INDEX_USE_STRING) { // If KeyboardEvent.keyCode is 0, it may be uninitialized. If so, we may // be able to decide a good .keyCode value if the .key value is a // non-printable key.
aKeyboardEvent.mKeyCode =
WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(
aKeyboardEvent.mKeyNameIndex);
}
// When this emulates real input only in content process, we need to // initialize edit commands with the main process's widget via PuppetWidget // because they are initialized by BrowserParent before content process treats // them. // And also when this synthesizes keyboard events for tests, we need default // shortcut keys on the platform for making any developers get constant // results in any environments.
// Note that retrieving edit commands via PuppetWidget is expensive. // Let's skip it when the keyboard event is inputting text. if (aKeyboardEvent.IsInputtingText()) {
aKeyboardEvent.PreventNativeKeyBindings(); return NS_OK;
}
// FYI: WidgetKeyboardEvent::InitAllEditCommands() isn't available here // since it checks whether it's called in the main process to // avoid performance issues so that we need to initialize each // command manually here. if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
NativeKeyBindingsType::SingleLineEditor, writingMode))) { return NS_ERROR_NOT_AVAILABLE;
} if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
NativeKeyBindingsType::MultiLineEditor, writingMode))) { return NS_ERROR_NOT_AVAILABLE;
} if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
NativeKeyBindingsType::RichTextEditor, writingMode))) { return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
}
NS_IMETHODIMP
TextInputProcessor::Keydown(Event* aDOMKeyEvent, uint32_t aKeyFlags,
uint8_t aOptionalArgc, uint32_t* aConsumedFlags) {
MOZ_RELEASE_ASSERT(aConsumedFlags, "aConsumedFlags must not be nullptr");
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); if (!aOptionalArgc) {
aKeyFlags = 0;
} if (NS_WARN_IF(!aDOMKeyEvent)) { return NS_ERROR_INVALID_ARG;
}
WidgetKeyboardEvent* originalKeyEvent =
aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); if (NS_WARN_IF(!originalKeyEvent)) { return NS_ERROR_INVALID_ARG;
} return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags);
}
if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) {
ModifierKeyData modifierKeyData(keyEvent); if (WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) { // If the modifier key is lockable modifier key such as CapsLock, // let's toggle modifier key state at keydown.
ToggleModifierKey(modifierKeyData);
} else { // Activate modifier flag before dispatching keydown event (i.e., keydown // event should indicate the releasing modifier is active.
ActivateModifierKey(modifierKeyData);
} if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) { return NS_OK;
}
} elseif (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) { return NS_ERROR_INVALID_ARG;
}
keyEvent.mModifiers = GetActiveModifiers();
nsEventStatus status =
aConsumedFlags ? nsEventStatus_eConsumeNoDefault : nsEventStatus_eIgnore; if (!kungFuDeathGrip->DispatchKeyboardEvent(eKeyDown, keyEvent, status)) { // If keydown event isn't dispatched, we don't need to dispatch keypress // events. return NS_OK;
}
// Only `eKeyPress` events, editor wants to execute system default edit // commands mapped to the key combination. In e10s world, edit commands can // be retrieved only in the parent process due to the performance reason. // Therefore, BrowserParent initializes edit commands for all cases before // sending the event to focused content process. For emulating this, we // need to do it now for synthesizing `eKeyPress` events if and only if // we're dispatching the events in a content process. if (XRE_IsContentProcess()) {
nsresult rv = InitEditCommands(keyEvent); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
} if (kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) {
aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault)
? KEYPRESS_IS_CONSUMED
: KEYEVENT_NOT_CONSUMED;
}
return NS_OK;
}
NS_IMETHODIMP
TextInputProcessor::Keyup(Event* aDOMKeyEvent, uint32_t aKeyFlags,
uint8_t aOptionalArgc, bool* aDoDefault) {
MOZ_RELEASE_ASSERT(aDoDefault, "aDoDefault must not be nullptr");
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); if (!aOptionalArgc) {
aKeyFlags = 0;
} if (NS_WARN_IF(!aDOMKeyEvent)) { return NS_ERROR_INVALID_ARG;
}
WidgetKeyboardEvent* originalKeyEvent =
aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); if (NS_WARN_IF(!originalKeyEvent)) { return NS_ERROR_INVALID_ARG;
} return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault);
}
WidgetKeyboardEvent keyEvent(true, eKeyPress, nullptr); if (originalKeyEvent) {
keyEvent = WidgetKeyboardEvent(*originalKeyEvent);
keyEvent.mFlags.mIsTrusted = true;
keyEvent.mMessage = eKeyPress;
}
keyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
keyEvent.mKeyValue = aString;
rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
} // Do not dispatch modifier key events even if the source event is a modifier // key event because modifier state should be changed before this. // TODO: In some test scenarios, we may need a new flag to use the given // modifier state as-is.
keyEvent.mModifiers = GetActiveModifiers();
// See KeyDownInternal() for the detail of this. if (XRE_IsContentProcess()) {
nsresult rv = InitEditCommands(keyEvent); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
}
nsEventStatus status =
*aDoDefault ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault;
RefPtr<TextEventDispatcher> dispatcher(mDispatcher); if (dispatcher->MaybeDispatchKeypressEvents(keyEvent, status)) {
*aDoDefault = (status != nsEventStatus_eConsumeNoDefault);
}
return NS_OK;
}
NS_IMETHODIMP
TextInputProcessor::GetModifierState(const nsAString& aModifierKeyName, bool* aActive) {
MOZ_RELEASE_ASSERT(aActive, "aActive must not be null");
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName);
*aActive = ((GetActiveModifiers() & modifier) != 0); return NS_OK;
}
NS_IMETHODIMP
TextInputProcessor::ShareModifierStateOf(nsITextInputProcessor* aOther) {
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); if (!aOther) {
mModifierKeyDataArray = nullptr; return NS_OK;
}
TextInputProcessor* other = static_cast<TextInputProcessor*>(aOther); if (!other->mModifierKeyDataArray) {
other->mModifierKeyDataArray = new ModifierKeyDataArray();
}
mModifierKeyDataArray = other->mModifierKeyDataArray; return NS_OK;
}
Maybe<uint32_t> location; if (aOptionalArgc) { if (aLocation.isNullOrUndefined()) { // location should be nothing.
} elseif (aLocation.isInt32()) {
location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32()));
} else {
NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(), "aLocation must be undefined, null or int"); return NS_ERROR_INVALID_ARG;
}
}
CodeNameIndex codeNameIndex =
GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(aKeyValue, location); if (codeNameIndex == CODE_NAME_INDEX_UNKNOWN) { return NS_OK;
}
MOZ_ASSERT(codeNameIndex != CODE_NAME_INDEX_USE_STRING);
WidgetKeyboardEvent::GetDOMCodeName(codeNameIndex, aCodeValue); return NS_OK;
}
// static
CodeNameIndex
TextInputProcessor::GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout( const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) { if (aKeyValue.IsEmpty()) { return CODE_NAME_INDEX_UNKNOWN;
} // US keyboard layout can input only one character per key. So, we can // assume that if the key value is 2 or more characters, it's a known // key name or not a usual key emulation. if (aKeyValue.Length() > 1) { return CODE_NAME_INDEX_UNKNOWN;
} if (aLocation.isSome() &&
aLocation.value() ==
dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) { switch (aKeyValue[0]) { case'+': return CODE_NAME_INDEX_NumpadAdd; case'-': return CODE_NAME_INDEX_NumpadSubtract; case'*': return CODE_NAME_INDEX_NumpadMultiply; case'/': return CODE_NAME_INDEX_NumpadDivide; case'.': return CODE_NAME_INDEX_NumpadDecimal; case'0': return CODE_NAME_INDEX_Numpad0; case'1': return CODE_NAME_INDEX_Numpad1; case'2': return CODE_NAME_INDEX_Numpad2; case'3': return CODE_NAME_INDEX_Numpad3; case'4': return CODE_NAME_INDEX_Numpad4; case'5': return CODE_NAME_INDEX_Numpad5; case'6': return CODE_NAME_INDEX_Numpad6; case'7': return CODE_NAME_INDEX_Numpad7; case'8': return CODE_NAME_INDEX_Numpad8; case'9': return CODE_NAME_INDEX_Numpad9; default: return CODE_NAME_INDEX_UNKNOWN;
}
}
if (aLocation.isSome() &&
aLocation.value() !=
dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) { return CODE_NAME_INDEX_UNKNOWN;
}
// static
uint32_t TextInputProcessor::GuessKeyCodeOfPrintableKeyInUSEnglishLayout( const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) { if (aKeyValue.IsEmpty()) { return 0;
} // US keyboard layout can input only one character per key. So, we can // assume that if the key value is 2 or more characters, it's a known // key name of a non-printable key or not a usual key emulation. if (aKeyValue.Length() > 1) { return 0;
}
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.