Bug 1844905 - Part 3. Basic keyboard support. r=masayuki

Implement UIKeyInput protocol to simple basic input.

When implementing UITextInput, we need to consider to share cocoa's code.

Differential Revision: https://phabricator.services.mozilla.com/D184286
This commit is contained in:
Mike Hommey 2024-03-04 21:03:25 +00:00
Родитель 23ae49e04d
Коммит e79a84dad4
6 изменённых файлов: 434 добавлений и 7 удалений

Просмотреть файл

@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et 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/. */
#ifndef TextInputHandler_h_
#define TextInputHandler_h_
#import <UIKit/UITextInput.h>
#include "mozilla/EventForwards.h"
#include "mozilla/TextEventDispatcherListener.h"
#include "mozilla/widget/IMEData.h"
#include "nsCOMPtr.h"
class nsWindow;
namespace mozilla::widget {
class TextEventDispatcher;
// This is the temporary input class. When implementing UITextInpt protocol, we
// should share this class with Cocoa's version.
class TextInputHandler final : public TextEventDispatcherListener {
public:
TextInputHandler(nsWindow* aWidget);
TextInputHandler() = delete;
NS_DECL_ISUPPORTS
NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
const IMENotification& aNotification) override;
NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
NS_IMETHOD_(void) OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
NS_IMETHOD_(void)
WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
void* aData) override;
// UIKeyInput delegation
bool InsertText(NSString* aText);
bool HandleCommand(Command aCommand);
void OnDestroyed();
private:
virtual ~TextInputHandler() = default;
bool DispatchKeyDownEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t aCharCode,
nsEventStatus& aStatus);
bool DispatchKeyUpEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t aCharCode,
nsEventStatus& aStatus);
bool DispatchKeyPressEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t aCharCode,
nsEventStatus& aStatus);
bool EmulateKeyboardEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t charCode);
bool Destroyed() { return !mWidget; }
nsWindow* mWidget; // weak ref
RefPtr<TextEventDispatcher> mDispatcher;
};
} // namespace mozilla::widget
#endif

Просмотреть файл

@ -0,0 +1,254 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et 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/. */
#include "TextInputHandler.h"
#import <UIKit/UIKit.h>
#include "mozilla/EventForwards.h"
#include "mozilla/Logging.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/WidgetUtils.h"
#include "nsIWidget.h"
#include "nsObjCExceptions.h"
#include "nsString.h"
#include "nsWindow.h"
mozilla::LazyLogModule gIMELog("TextInputHandler");
namespace mozilla::widget {
static void GetStringForNSString(const NSString* aSrc, nsAString& aDist) {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (!aSrc) {
aDist.Truncate();
return;
}
aDist.SetLength([aSrc length]);
[aSrc getCharacters:reinterpret_cast<unichar*>(aDist.BeginWriting())
range:NSMakeRange(0, [aSrc length])];
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
NS_IMPL_ISUPPORTS(TextInputHandler, TextEventDispatcherListener,
nsISupportsWeakReference)
TextInputHandler::TextInputHandler(nsWindow* aWidget)
: mWidget(aWidget), mDispatcher(aWidget->GetTextEventDispatcher()) {}
nsresult TextInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
const IMENotification& aNotification) {
return NS_OK;
}
IMENotificationRequests TextInputHandler::GetIMENotificationRequests() {
return IMENotificationRequests();
}
void TextInputHandler::OnRemovedFrom(
TextEventDispatcher* aTextEventDispatcher) {}
void TextInputHandler::WillDispatchKeyboardEvent(
TextEventDispatcher* aTextEventDispatcher,
WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
void* aData) {}
bool TextInputHandler::InsertText(NSString* aText) {
nsString str;
GetStringForNSString(aText, str);
MOZ_LOG(gIMELog, LogLevel::Info,
("%p TextInputHandler::InsertText(aText=%s)", this,
NS_ConvertUTF16toUTF8(str).get()));
if (Destroyed()) {
return false;
}
if (str.Length() == 1) {
char16_t charCode = str[0];
if (charCode == 0x0a) {
return EmulateKeyboardEvent(NS_VK_RETURN, KEY_NAME_INDEX_Enter, charCode);
}
if (charCode == 0x08) {
return EmulateKeyboardEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace,
charCode);
}
if (uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode)) {
return EmulateKeyboardEvent(keyCode, KEY_NAME_INDEX_USE_STRING, charCode);
}
}
nsEventStatus status = nsEventStatus_eIgnore;
RefPtr<nsWindow> widget(mWidget);
if (!DispatchKeyDownEvent(NS_VK_PROCESSKEY, KEY_NAME_INDEX_Process, 0,
status)) {
return false;
}
if (Destroyed()) {
return false;
}
mDispatcher->CommitComposition(status, &str, nullptr);
if (widget->Destroyed()) {
return false;
}
DispatchKeyUpEvent(NS_VK_PROCESSKEY, KEY_NAME_INDEX_Process, 0, status);
return true;
}
bool TextInputHandler::HandleCommand(Command aCommand) {
MOZ_LOG(gIMELog, LogLevel::Info,
("%p TextInputHandler::HandleCommand, aCommand=%s", this,
ToChar(aCommand)));
if (Destroyed()) {
return false;
}
if (aCommand != Command::DeleteCharBackward) {
return false;
}
nsEventStatus status = nsEventStatus_eIgnore;
if (!DispatchKeyDownEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace, 0, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}
// TODO: Focus check
if (!DispatchKeyPressEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace, 0, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}
// TODO: Focus check
DispatchKeyUpEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace, 0, status);
return true;
}
static uint32_t ComputeKeyModifiers(uint32_t aCharCode) {
if (aCharCode >= 'A' && aCharCode <= 'Z') {
return MODIFIER_SHIFT;
}
return 0;
}
static void InitKeyEvent(WidgetKeyboardEvent& aEvent, uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex, char16_t aCharCode) {
aEvent.mKeyCode = aKeyCode;
aEvent.mIsRepeat = false;
aEvent.mKeyNameIndex = aKeyNameIndex;
// TODO(m_kato):
// How to get native key? Then, implement NativeKeyToDOM*.h for iOS
aEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
if (aEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
aEvent.mKeyValue = aCharCode;
}
aEvent.mModifiers = ComputeKeyModifiers(aCharCode);
aEvent.mLocation = eKeyLocationStandard;
aEvent.mTimeStamp = TimeStamp::Now();
}
bool TextInputHandler::DispatchKeyDownEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode,
nsEventStatus& aStatus) {
MOZ_ASSERT(aKeyCode);
MOZ_ASSERT(mWidget);
WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
InitKeyEvent(keydownEvent, aKeyCode, aKeyNameIndex, aCharCode);
nsresult rv = mDispatcher->BeginNativeInputTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("BeginNativeInputTransaction is failed");
return false;
}
return mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, aStatus);
}
bool TextInputHandler::DispatchKeyUpEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode,
nsEventStatus& aStatus) {
MOZ_ASSERT(aKeyCode);
MOZ_ASSERT(mWidget);
WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
InitKeyEvent(keyupEvent, aKeyCode, aKeyNameIndex, aCharCode);
nsresult rv = mDispatcher->BeginNativeInputTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("BeginNativeInputTransaction is failed");
return false;
}
return mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, aStatus);
}
bool TextInputHandler::DispatchKeyPressEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode,
nsEventStatus& aStatus) {
MOZ_ASSERT(aKeyCode);
MOZ_ASSERT(mWidget);
WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
InitKeyEvent(keypressEvent, aKeyCode, aKeyNameIndex, aCharCode);
nsresult rv = mDispatcher->BeginNativeInputTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("BeginNativeInputTransaction is failed");
return false;
}
return mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, aStatus);
}
bool TextInputHandler::EmulateKeyboardEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode) {
MOZ_ASSERT(aCharCode);
MOZ_LOG(gIMELog, LogLevel::Info,
("%p TextInputHandler::EmulateKeyboardEvent(aKeyCode=%x, "
"aKeyNameIndex=%x, aCharCode=%x)",
this, aKeyCode, aKeyNameIndex, aCharCode));
nsEventStatus status = nsEventStatus_eIgnore;
if (!DispatchKeyDownEvent(aKeyCode, aKeyNameIndex, aCharCode, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}
// TODO: Focus check
if (!DispatchKeyPressEvent(aKeyCode, aKeyNameIndex, aCharCode, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}
// TODO: Focus check
DispatchKeyUpEvent(aKeyCode, aKeyNameIndex, aCharCode, status);
return true;
}
void TextInputHandler::OnDestroyed() { mWidget = nullptr; }
} // namespace mozilla::widget

Просмотреть файл

@ -7,12 +7,16 @@
with Files("**"):
BUG_COMPONENT = ("Core", "Widget")
with Files("*TextInput*"):
BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
SOURCES += [
"GfxInfo.cpp",
"nsAppShell.mm",
"nsLookAndFeel.mm",
"nsWidgetFactory.mm",
"nsWindow.mm",
"TextInputHandler.mm",
]
include("/ipc/chromium/chromium-config.mozbuild")

Просмотреть файл

@ -15,6 +15,10 @@
@class UIView;
@class ChildView;
namespace mozilla::widget {
class TextInputHandler;
}
class nsWindow final : public nsBaseWidget {
typedef nsBaseWidget Inherited;
@ -79,6 +83,13 @@ class nsWindow final : public nsBaseWidget {
void SetInputContext(const InputContext& aContext,
const InputContextAction& aAction) override;
InputContext GetInputContext() override;
TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;
mozilla::widget::TextInputHandler* GetTextInputHandler() const {
return mTextInputHandler;
}
bool IsVirtualKeyboardDisabled() const;
/*
virtual bool ExecuteNativeKeyBinding(
NativeKeyBindingsType aType,
@ -102,7 +113,9 @@ class nsWindow final : public nsBaseWidget {
nsSizeMode mSizeMode;
nsTArray<nsWindow*> mChildren;
nsWindow* mParent;
InputContext mInputContext;
mozilla::widget::InputContext mInputContext;
RefPtr<mozilla::widget::TextInputHandler> mTextInputHandler;
void OnSizeChanged(const mozilla::gfx::IntSize& aSize);

Просмотреть файл

@ -27,8 +27,10 @@
#include "gfxUtils.h"
#include "gfxImageSurface.h"
#include "gfxContext.h"
#include "nsObjCExceptions.h"
#include "nsRegion.h"
#include "nsTArray.h"
#include "TextInputHandler.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/ProfilerLabels.h"
@ -70,7 +72,7 @@ class nsAutoRetainUIKitObject {
id mObject; // [STRONG]
};
@interface ChildView : UIView {
@interface ChildView : UIView <UIKeyInput> {
@public
nsWindow* mGeckoChild; // weak ref
BOOL mWaitingForPaint;
@ -278,6 +280,17 @@ class nsAutoRetainUIKitObject {
widget:mGeckoChild];
}
- (BOOL)canBecomeFirstResponder {
if (!mGeckoChild) {
return NO;
}
if (mGeckoChild->IsVirtualKeyboardDisabled()) {
return NO;
}
return YES;
}
- (void)setNeedsDisplayInRect:(CGRect)aRect {
if ([self isUsingMainThreadOpenGL]) {
// Draw without calling drawRect. This prevent us from
@ -441,6 +454,44 @@ class nsAutoRetainUIKitObject {
CGContextStrokeRect(aContext, aRect);
#endif
}
// UIKeyInput
- (void)insertText:(NSString*)text {
if (!mGeckoChild || mGeckoChild->Destroyed()) {
return;
}
widget::TextInputHandler* textInputHandler =
mGeckoChild->GetTextInputHandler();
if (!textInputHandler) {
return;
}
textInputHandler->InsertText(text);
}
- (void)deleteBackward {
if (!mGeckoChild || mGeckoChild->Destroyed()) {
return;
}
widget::TextInputHandler* textInputHandler =
mGeckoChild->GetTextInputHandler();
if (!textInputHandler) {
return;
}
textInputHandler->HandleCommand(Command::DeleteCharBackward);
}
- (BOOL)hasText {
if (!mGeckoChild || mGeckoChild->Destroyed()) {
return NO;
}
widget::InputContext context = mGeckoChild->GetInputContext();
if (context.mIMEState.mEnabled == mozilla::widget::IMEEnabled::Disabled) {
return NO;
}
return YES;
}
@end
nsWindow::nsWindow()
@ -516,6 +567,8 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
[nsAppShell::gTopLevelViews addObject:mNativeView];
}
mTextInputHandler = new widget::TextInputHandler(this);
return NS_OK;
}
@ -527,6 +580,11 @@ void nsWindow::Destroy() {
if (mParent) mParent->mChildren.RemoveElement(this);
if (mTextInputHandler) {
mTextInputHandler->OnDestroyed();
}
mTextInputHandler = nullptr;
[mNativeView widgetDestroyed];
nsBaseWidget::Destroy();
@ -723,14 +781,44 @@ nsresult nsWindow::DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
void nsWindow::SetInputContext(const InputContext& aContext,
const InputContextAction& aAction) {
// TODO: actually show VKB
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
mInputContext = aContext;
if (IsVirtualKeyboardDisabled()) {
[mNativeView resignFirstResponder];
return;
}
[mNativeView becomeFirstResponder];
if (aAction.UserMightRequestOpenVKB()) {
[mNativeView reloadInputViews];
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
mozilla::widget::InputContext nsWindow::GetInputContext() {
widget::InputContext nsWindow::GetInputContext() {
if (!mTextInputHandler) {
InputContext context;
context.mIMEState.mEnabled = IMEEnabled::Disabled;
context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
return context;
}
return mInputContext;
}
widget::TextEventDispatcherListener*
nsWindow::GetNativeTextEventDispatcherListener() {
return mTextInputHandler;
}
bool nsWindow::IsVirtualKeyboardDisabled() const {
return mInputContext.mIMEState.mEnabled == IMEEnabled::Disabled ||
mInputContext.mHTMLInputMode.EqualsLiteral("none");
}
void nsWindow::SetBackgroundColor(const nscolor& aColor) {
mNativeView.backgroundColor = [UIColor colorWithRed:NS_GET_R(aColor)
green:NS_GET_G(aColor)

Просмотреть файл

@ -27,9 +27,6 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
XPIDL_SOURCES += [
"nsIMacPreferencesReader.idl",
]
EXPORTS += [
"nsObjCExceptions.h",
]
EXPORTS.mozilla += [
"MacHelpers.h",
"MacStringHelpers.h",
@ -39,6 +36,12 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
"MacHelpers.mm",
"MacStringHelpers.mm",
"nsMacPreferencesReader.mm",
]
if CONFIG["OS_ARCH"] == "Darwin":
EXPORTS += [
"nsObjCExceptions.h",
]
UNIFIED_SOURCES += [
"nsObjCExceptions.mm",
]