AquaSKK 4.5.0ではEscキーの扱いを改善した。 この修正のためにWebKitにおけるキーイベント配信の仕組みを追ったので、まとめる。
💡入力メソッドごとの差異
上記Tweetにあるように、テキストを入力中にEscを押した際にJavaScriptで発火するイベントが入力メソッドごとに違う。
macOS標準の日本語入力やGoogle日本語入力ではキーコードが229のイベントが発生するが、AquaSKKではキーコードが27のイベントが発生していた。 このイベントが違うと入力メソッドを扱うJavaScriptコードが複雑化するので修正した。
🔑キーコード 229
キーコード229はUI Eventsで次のように定義されている。
If an Input Method Editor is processing key input and the event is keydown, return 229.
(訳: 入力メソッドがキー入力を処理しておりイベントが
keydown
の場合は、キーコードは229となる)
🌐WebKit
W3Cが定めているキーコードなのでブラウザ内部の処理が関係するのだろうと想定して、WebKitのコードを調べた。
WebKitはアプリケーションのUIを実現するUIプロセスとタブごとに作られるWebプロセスが協調して動作している。 キー入力の処理もこの2つのプロセスが関連している。
UIプロセス
// Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm void WebViewImpl::keyDown(NSEvent *event) { // (snip) // 入力メソッドに処理を転送する interpretKeyEvent(event, [weakThis = createWeakPtr(), capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) { ASSERT(!handledByInputMethod || commands.isEmpty()); // キーイベントに入力メソッドが処理したかどうか(handledByInputMethod)を付与して、Webプロセスに転送する if (weakThis) weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, weakThis->m_isTextInsertionReplacingSoftSpace, commands)); }); } void WebViewImpl::interpretKeyEvent(NSEvent *event, void(^completionHandler)(BOOL handled, const Vector<WebCore::KeypressCommand>& commands)) { // (snip) // Cocoaが提供するAPIを使ってイベントを入力メソッドに転送する [inputContext() handleEventByInputMethod:event completionHandler:[weakThis = createWeakPtr(), capturedEvent = retainPtr(event), capturedBlock = makeBlockPtr(completionHandler)](BOOL handled) { if (!weakThis) { capturedBlock(NO, { }); return; } LOG(TextInput, "... handleEventByInputMethod%s handled", handled ? "" : " not"); if (handled) { capturedBlock(YES, { }); return; } auto commands = weakThis->collectKeyboardLayoutCommandsForEvent(capturedEvent.get()); capturedBlock(NO, commands); }]; }
入力メソッドにキー入力を転送するのに非公開のAPIを利用している。愉快。公開されているAPIと違い、処理結果をコールバックで受けとれるようになっている。
// FIXME: Move to an SPI header. @interface NSTextInputContext (WKNSTextInputContextDetails) - (void)handleEvent:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler; - (void)handleEventByInputMethod:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler; - (BOOL)handleEventByKeyboardLayout:(NSEvent *)event; @end
Webプロセス
WebプロセスではUIプロセスで付けられたマークをもとに、元のキーイベントを配信するかキーコード229のイベントを配信するかを決めている。
// Soruce/WebCore/page/EventHandler.cpp // Match key code of composition keydown event on windows. // IE sends VK_PROCESSKEY which has value 229; const int CompositionEventKeyCode = 229; bool EventHandler::internalKeyEvent(const PlatformKeyboardEvent& initialKeyEvent) { // (snip) // キー入力が入力メソッドで処理ずみかどうかを判定する bool handledByInputMethod = keydown->defaultHandled(); if (handledByInputMethod) { // 入力メソッドで処理済みだったらキーコードを229に入れ替える keyDownEvent.setWindowsVirtualKeyCode(CompositionEventKeyCode); keydown = KeyboardEvent::create(keyDownEvent, &m_frame.windowProxy()); keydown->setTarget(element); keydown->setDefaultHandled(); } // (snip) // キーイベントを配信する element->dispatchEvent(keydown); if (handledByInputMethod) return true; // 入力メソッドで処理されていないなら処理を継続する // (snip) }
🙊余談
- JSに配信されるイベントを見るにはKeyboard Event Viewerが便利。
- WebKitを処理を追うときはXcodeでブレイクポイントを設定して追った。 Debugging WebKitが参考になる。
- WebKitのビルドに時間がかかってつらかったので、途中で会社のiMacProに接続してビルドしはじめた。 あとからビルドを始めたのに、先にビルドが終わったのでびびった。