2014 年やったこと - Mozc 編

いつもどおりコードが公開されている範囲で.

実は 2013 年にやっていたこと

OSS Mozc のアップデートタイミングの関係上,2013年やったこと - Mozc 編 - NyaRuRuが地球にいたころ に載っていなかった内容です.概ね 2013 年末休暇シーズンに余暇としてやっていました.

Windows 版での候補ウィンドウのカラー絵文字対応

Mozc チームを離れる直前,休暇シーズンということもあって久しぶりに自分の書きたいコードでも書くかと言うことでやってみたのがこのカラー絵文字対応です.
f:id:NyaRuRu:20150211174200p:plain
作業のほとんどは mozc_renderer (独立したプロセスとして動く,候補ウィンドウ表示用プロセス) 内の文字表示処理を DirectWrite に対応させることでした.カラー絵文字専用に追加したコードは数行です.
r192 にてリリース.
変更の大半は renderer/win32/text_renderer.cc にまとまっています.

候補ウィンドウの DirectWrite 対応

思い起こせば 2009 年,Mozc の候補ウィンドウ表示を別プロセス化したころから DirectWrite を使うというアイディア自体はありました.緊急性と重要性の高いタスクは他にいくらでもあったことからこのアイディアは長らく放置されていたのですが,カラー絵文字表示の副産物として 4 年越しの実現となります.Windows 8 以前の OS でも,高 DPI 環境ではそれなりにメリットがあるかもしれません.
r192 にてリリース.

ITfFnReconversion::GetReconversion 対応

tsf-mozc が ITfFnReconversion::GetReconversion をサポートしていなかったので実装してみた,というものです.ITfFnReconversion::GetReconversion を使用する一部アプリケーション以外でユーザーの目に見える変化はありません.

一番わかりやすいのは Microsoft Word で,文字を選択したあと右クリックすると,コンテキストメニュー内に再変換候補が表示されるようになります.
f:id:NyaRuRu:20150211163902p:plain:h600
Mozc 内部のプロトコル的に再変換は対応済みだったので,あとは単純に実装するだけという内容でした.

2014 年にやったこと

2013 年 12 月末をもって所属的には Mozc チームを離れたこともあり,休日・休暇に作業できる内容であることというのを特に重視していた 2014 年でした.そんなわけで注目してみたのが,OSS 版周りの自主オープンソース活動でした.OSS としてみた Mozc プロジェクトは,特に対外的な締め切りもなく,本当に時間的に余裕があるときだけ作業するのに適しています.また,OSS 化後のソースコードについては個人の PC 環境で作業できるので,喫茶店や旅行先でコードを見たり変更したりしながらあれこれ試せるのも嬉しいポイントでした.

OSS Mozc を選んだもうひとつの理由としては,最近モバイル OS 向けに OSS Mozc をベースにした IME を作っていただけるケースが増えていて,少し応援してみたくなったというのもあります.数字的にも合計数百万インストールという規模で,OSS としてはまずまずの成果.間接的にモバイル IME 開発の活発化に貢献できるのであれば,休日の使い方としてはまずまず Mobile First かなと思った次第.

さて,2014 年のテーマとして個人的に興味があったのは,社内リポジトリから OSS リポジトリへに変更を移動するとき,そのコミット粒度を小さくするのは可能か,というものでした.

これまで OSS Mozc といえば,いわゆる "code drop" 方式でリリースされていました.開発自体は社内のリポジトリで行い,しかるべきタイミングに (たいていはプロプライタリな製品として社外にリリースされたあと),開発されたソースコードを OSS として公開する,というものです.このとき問題になるのがコミットログで,OSS Mozc は様々な理由から変更は 1 つの SVN コミットにまとめられ,数千行から多いときは数万行の変更になることもありました.この巨大なコミットに含まれる変更点全てを把握するは開発者自身にとっても難しく,コミットログやリリースノートを書くときに苦労したのを憶えています.

そこで試してみることにしたのが,OSS 化して問題ない変更については,確認の上,個別のコミットとして OSS リポジトリに適宜反映させていくというものでした.作業自体は休日等の空き時間を利用して行っています.2014年5月24日から12月25日の間に,222 個のコミットを行いました (うち 81 コミットは 2014年12月6日から12月31日までの間,有給消化の年末休暇中に行っています).

残念ながら r467 だけは従来通りの巨大コミットになってしまいましたが,これは Android 向け新機能が多数含まれるため分けきれなかったというのが理由です.それ以前の 218 個のコミットはコミットメッセージと変更が (概ね) 対応した通常のコミットに見えるようになっています.変更の粒度を下げたことで,最後の巨大コミットを待つことなく,27 個のバグを閉じることができました.

この作業,個人的に何が新鮮に感じたかというと,それがある種の <コミット後レビュー> だったことです (このあたりの話,コードレビューいろいろ - steps to phantasien を先に読むことをおすすめ).会社に入って以来,<コミット前レビュー> は 6 年ぐらいやってきたものの,<コミット後レビュー> を継続的にやったのはこれが初めてでした.
感想 1.既にコミット済みのコードに対するレビューなので,気になるところを修正するとそれが別コミットになってしまう.
感想 2.ある程度の期間のコミットの差分をまとめて見たことで,無駄なコードが増えたのに気付きやすくなる.以下典型的なケース.

  1. ある問題に,A という方法で対処する
  2. A という方法では完全に問題が解決できないことが分かり B という方法に切り替える

複数のコミットが数日から数週間の間に加えられていたとき.あとで全体の差分を見てみると不要な変更が残ってしまっているケースを数件発見.これは確かに <コミット前レビュー> で見つけにくいパターンかも.

まあそんなわけで新鮮でもあった細切れ OSS 化作業ですが,休日の時間を結構吸い取られる作業であったのも確かで,今後どうするかはまだ決めていません.

一方,OSS 版リポジトリを活用し,実際にバグを直したり技術的負債を返済したりといったことも行っていました.以下は,そんな休日自主オープンソース活動についてです.

TF_E_NOLAYOUT 問題

Mozilla Japan の中野さんから報告していただいた問題です.調査の結果,以下のような OS 側の問題であることが分かりました.

  1. TSF で書かれた IME (TIP) が ITfContextView::GetTextExt() を呼び出す.
  2. TSF ランタイムがアプリケーションの実装する ITextStoreACP::GetTextExt() を呼び出す
  3. アプリケーションが ITextStoreACP::GetTextExt() に対して TS_E_NOLAYOUT を返す
  4. TSF ランタイムが戻り値を E_FAIL に書き換える
  5. TSF で書かれた IME (TIP) が ITfContextView::GetTextExt() の結果として E_FAIL を受け取る

開発元のサポートに相談したところ,ITfContextView::GetACPFromPoint() と ITfContextView:: GetRangeFromPoint() にはこの問題がないことが分かり,TS_E_NOLAYOUT が返されているかどうかの判定をこれらの API で行うという回避策をおすすめされました.その方針で tsf-mozc 側にて対処してみた,というのが一連の流れとなります.
r192 にてリリース.コード的には以下となります.
https://code.google.com/p/mozc/source/browse/trunk/src/win32/tip/tip_range_util.cc?r=192#231

HRESULT TipRangeUtil::GetTextExt(ITfContextView *context_view,
                                 TfEditCookie read_cookie,
                                 ITfRange *range,
                                 RECT *rect,
                                 bool *clipped) {
  BOOL clipped_result = FALSE;
  HRESULT hr = context_view->GetTextExt(
      read_cookie, range, rect, &clipped_result);
  if (clipped != nullptr) {
    *clipped = (clipped_result != FALSE);
  }
  // Due to a bug of TSF subsystem, as of Windows 8.1 and prior,
  // ITfContextView::GetTextExt never returns TF_E_NOLAYOUT even when an
  // application returns TS_E_NOLAYOUT in ITextStoreACP::GetTextExt. Since this
  // bug is specific to ITfContextView::GetTextExt, a possible workaround is
  // to also see the returned value of ITfContextView::GetRangeFromPoint.
  // If the attached application implements ITextStoreACP::GetACPFromPoint
  // consistently with ITfContextView::GetTextExt,, we can suspect if E_FAIL
  // returned from ITfContextView::GetTextExt comes from TF_E_NOLAYOUT.
  if (hr == E_FAIL) {
    // Depending on the internal design of an application, the implementation of
    // ITextStoreACP::GetACPFromPoint can easily be computationally expensive.
    // This is why we should carefully choose parameters passed to
    // ITfContextView::GetRangeFromPoint here.
    const POINT dummy_point = {
       numeric_limits<LONG>::min(), numeric_limits<LONG>::min()
    };
    CComPtr<ITfRange> dummy_range;
    const HRESULT next_hr = context_view->GetRangeFromPoint(
         read_cookie, &dummy_point, 0, &dummy_range);
    if (next_hr == TF_E_NOLAYOUT) {
      hr = TF_E_NOLAYOUT;
    }
  }
  return hr;
}

ちなみにこの問題,現在ベータテスト中の Windows 10 では OS 側で無事修正されている模様です.


TSF を有効にした Firefox がフォーカスを失ったとき候補ウィンドウが表示されたままになる

tsf-mozc と TSF-aware Firefox の組み合わせで,文字入力中に別アプリケーションにフォーカスを移すと,Mozc の候補ウィンドウが表示されたままになるという問題です.
tsf-mozc 側で ITfThreadFocusSink::OnSetThreadFocus() と ITfThreadFocusSink::OnKillThreadFocus() を処理していなかったのが問題でした.
r192 にてリリース.

Issue 206: 特定ロケールの Windows 環境で prediction/dictionary_predictor.cc をビルドすると C2065 エラーで失敗する

https://code.google.com/p/mozc/issues/detail?id=206
Mozcの C++ で書かれたソースコードは基本的に BOM なし UTF-8 で統一されているのですが,過去何度もVisual C++ が C2065 エラーでコンパイルに失敗するというトラブルに見舞われて来ました.そこで発明された対処法が,とりあえず non-ASCII 文字は "" で囲む,というものです.
OSS Mozc の方に C2065 の報告が来たわけですが,今回も "" で囲って対処となりました.
r192 にてリリース.

Issue 215: mozc::Encryptor 実装のポータブル化

https://code.google.com/p/mozc/issues/detail?id=215
mozc::Encryptor というクラスの実装から,プラットフォーム別の処理を極力取り除いたというものです.
Mozc は,確定履歴などいくつかのローカルファイルを保存時に AES256 CBC モードで暗号化します (Design Doc 参照)
当時この実装はプラットフォームごとに異なっていて, Mac 版, NaCl 版,および Linux デスクトップ版 Mozc では OpenSSL が,Windows 版では Win32 Crypto APIが,そしてAndroid 版では javax.crypto.Cipher クラスが使われていました.
さて,話は 2014 年 2月22日にさかのぼります.当日 Mozilla Japan 東京オフィスで開かれていた FxOS コードリーディングミートアップ #4 にふらりと参加した筆者は,Mozc を Emscripten でビルドできないものかとあれこれ試し始めました.Mozc のビルドは概ね「近寄るべきではない程度に大変」の部類に属しますが,そうはいってもクロスコンパイルの必要な Android 対応と NaCl 対応は既にできているわけです.数時間もあれば最初の一歩ぐらいはなんとかなるだろうと軽い気持ちで始めたものの,結果的に最初の一歩すら踏み出せずに敗退することとなります.

このとき最初の一歩として立ちふさがったのが,この OpenSSL 依存でした.以下に,当時私が参考にしようとしていた nacl-mozc の OpenSSL 依存部分を示します.
https://code.google.com/p/mozc/source/browse/trunk/src/base/base.gyp?r=185#508

    ['target_platform=="NaCl"', {
      'targets': [
        {
          'target_name': 'pepper_file_system_mock',
          'type': 'static_library',
          'sources': [
            'pepper_file_system_mock.cc',
          ],
          'dependencies': [
            'base.gyp:base',
          ],
        },
        {
          'target_name': 'openssl_crypto_aes',
          'type': 'static_library',
          'variables': {
            'openssl_dir%': '<(DEPTH)/third_party/openssl/openssl',
          },
          'direct_dependent_settings': {
            'defines': [
              'HAVE_OPENSSL=1',
            ],
            'include_dirs': [
              '<(gen_out_dir)/openssl/include',
              '<(openssl_dir)/include',
            ],
          },
          'cflags': [
            # Suppresses warnings in third_party codes.
            '-Wno-unused-value',
          ],
          'include_dirs': [
            '<(gen_out_dir)/openssl/include',
            '<(openssl_dir)',
            '<(openssl_dir)/crypto',
            '<(openssl_dir)/include',
          ],
          'hard_dependency': 1,
          'sources': [
            # The following files are the minumum set for 'encryptor.cc'
            '<(openssl_dir)/crypto/aes/aes_cbc.c',
            '<(openssl_dir)/crypto/aes/aes_core.c',
            '<(openssl_dir)/crypto/aes/aes_misc.c',
            '<(openssl_dir)/crypto/mem_clr.c',
            '<(openssl_dir)/crypto/modes/cbc128.c',
            '<(openssl_dir)/crypto/sha/sha1dgst.c',
            '<(openssl_dir)/crypto/sha/sha1_one.c',
            '<(openssl_dir)/crypto/sha/sha256.c',
            '<(openssl_dir)/crypto/sha/sha512.c',
            '<(openssl_dir)/crypto/sha/sha_dgst.c',
            '<(openssl_dir)/crypto/sha/sha_one.c',
          ],
          'dependencies': [
            'openssl_config',
          ],
          'export_dependent_settings': [
            'openssl_config',
          ],
        },
        {
          'target_name': 'openssl_config',
          'type': 'none',
          'actions': [
            {
              'action_name': 'openssl_config',
              'inputs': [
                'openssl_config.sh',
              ],
              'outputs': [
                '<(gen_out_dir)/openssl/include/openssl/opensslconf.h',
              ],
              'action': [
                'bash', 'openssl_config.sh', '<(gen_out_dir)', '$(abspath ./)',
              ],
            },
          ],
        },
      ]},
    ],
  ],
}

ここでやっているのは,OpenSSL のソースコードがダウンロード済みと仮定して,configure スクリプトを走らせ,ターゲットの toolchain でビルドする,というものです.asm.js 版 Mozc を作るにあたって,これとほぼ同様の作業を行う必要がありました.一方 Android 版も, JNI を使って javax.crypto.Cipher クラスの一部を C++ に公開するという,処理的にもビルド設定的にも非常に複雑な実装になっていることが分かりました.
実は Mozc がローカルにファイルを保存するときの暗号化処理は,"カジュアルな" 情報漏洩対策という何とも微妙な目的で導入されたという過去があります.デスクトップ版のみであった最初期は,この機能による複雑さの増加もまだ許容できたのかもしれませんが, Android 版や NaCl 版開発時に「がんばって移植すべき機能」のひとつになってしまったのは不幸でした.これらの環境では,システムの OpenSSL を単純に呼び出すといった方法が使えなかったため,ビルド時に OpenSSL 自体をビルドしたり,JNI コードを追加したりといった方法で対処されることとなりました.このがんばりは,予定されたスケジュールまでに製品をリリースするという点では有効でしたが,残ることになった複雑さはまさに技術的負債でした.
さて,よくよく処理内容を調べてみると,SHA1 と AES256 CBC の計算で,きちんと認証を受けた OS 固有実装を利用しようとした結果,物事が複雑化しまっていることがわかります.しかしながら,もともと "カジュアルな" 情報漏洩対策でしかない暗号化処理で,FIPS 認定にこだわるというのも変な話です.だったら自前で SHA1 と AES256 CBC を実装してしまえば,大幅にビルドが単純になるだろう,と始めてみたのがこの Issue 215 となります.
基本実装は r192 に含まれていますが,この時点では旧実装も残されていました.その後,r208r209r497 として旧実装は削除されています.
規格を読みながら有名アルゴリズムを実装するのは楽しい体験でした.計算結果の比較のために見ていた Chromium の SHA1 実装にバグを見つけるというおまけもありました (Chromium Issue 348333) .4 月に OpenSSL の Heartbleed が盛り上がったのも,面白い偶然の一致です.
まとめると,以下のようなメリットがあったものと考えています.

  • 実行時に依存するライブラリが少なくなる
  • ビルドが単純化される
  • プラットフォーム共通コードが増える
  • (OSS Mozc に GPL コードを組み合わせたい人にとっての),潜在的なライセンス非互換問題のリスクが減る

Issue 211: 2 段階ビルドを廃止

https://code.google.com/p/mozc/issues/detail?id=211
従来,たとえば Android 版 Mozc の APK をビルドするときには,以下のようにコマンドを 3 回実行する必要がありました.

python build_mozc.py gyp --target_platform=Android
python build_mozc.py build_tools -c Release
python build_mozc.py build -c Debug_Android android/android.gyp:apk

これが,以下のようにコマンド 2 回でビルドできるようになった,というものです.

python build_mozc.py gyp --target_platform=Android
python build_mozc.py build -c Debug android/android.gyp:apk

Mozc のビルドが build_tools というコマンドと build の 2 段階に分けられたのは,実に 2010 年,Mozc を OSS 化するにあたってビルドシステムを GYP に移行するさなかのことでした.当初の動機は,時間のかかるバイナリ辞書圧縮処理を開発中何度も実行したくないという理由でした.状況が変わったのは,Android 版や NaCl 版の必要がでてきてからです.これらのプラットフォームはクロスコンパイルを必要とし,そのための基盤として 2 段階ビルドが「利用」されました.ビルドの複雑さは一時制御不能な規模までふくれあがり,一見正しく見える変更が,なぜか Android ビルドだけを壊すという問題が多発しました.記憶が確かなら 2011 年から 2012 年頃が最も酷かったように思います.
このひどい状況は,2013 年末,Android 版ビルドが GYP の Android.mk ジェネレータから一般的な make ジェネレータに切り替えられることで大幅に改善され,今回 FxOS コードリーディングミートアップ #4 に参加したのを切っ掛けに取り払ってみた感じです.
ちなみに 2010 年ごろ懸念されたビルド時間の問題も,今時のワークステーションで作業する分には概ね問題になりません.
基本実装は r192 にて.その後,r211およびr214にて有効化されました.
2 段階ビルドに使用されたコードはその後 r371 および r507 として完全に削除されます.

Issue 224: enable_gtk_renderer=0 指定時にビルドが失敗する

https://code.google.com/p/mozc/issues/detail?id=224
Linux 版 Mozc で, mozc_renderer を無効にした状態でビルドしようとするとコンパイルエラーになるという問題.単純なミスだったので即修正となりました.
r215 にて修正.

Unicode 絵文字変換を NaCl 環境および Linux デスクトップ環境でもデフォルト有効化

今までは特定環境のみ Unicode 絵文字をデフォルト有効化というロジックだったのですが,コードがだいぶ複雑になってしまっていたので,逆に特定環境のみデフォルト無効化というロジックに変更しました.このときの簡略化の一環で, NaCl 環境および Linux デスクトップ環境でも Unicode 絵文字変換はデフォルト有効化となりました.
r229r230r231 の 3 つのコミットにより有効化.

Issue 226: ibus-mozc の周辺文字列取得コードが正しく動作していなかった

https://code.google.com/p/mozc/issues/detail?id=226
ibus-mozc の周辺文字列の取得コードにバグがあって,UTF-8 文字列のバイトオフセットを使って部分文字列を切り出すべきところに,間違って文字数を使って部分文字列を切り出していたというもの.結果的に,ibus-mozc で周辺文字列を考慮した変換が意図しない形で動作してしまっていました.
r232 および r233 にて修正.

Issue 227: IBus 1.5 環境下で確定取り消しが動作しない

https://code.google.com/p/mozc/issues/detail?id=227
Issue 201 修正の副作用で,ibus-mozc を IBus 1.5 環境下で使用しているとき,確定取り消し (MS-IME キー設定で言うところの Ctrl+BS) が動作しなくなっていた,という問題の修正です.
r234 にて修正.

Issue 233: ibus-mozc 使用時,MozcTool に対応するテキストラベルが存在しない

https://code.google.com/p/mozc/issues/detail?id=233
MozcTool に対応するテキストラベルが存在せず,空白のエントリが表示されるというものです. IBus 1.4 環境までは言語バー的な UI が標準で表示されていたため問題になりませんでしたが,IBus 1.5 環境で言語バー的な UI が廃止されたためこの問題が表面化する可能性が高くなりました.
f:id:NyaRuRu:20150211174258p:plain
ちょうど社内のワークステーションが Ubuntu 14.04 に切り替わるタイミングだったので,同僚から言われる前に IBus 1.5 関係のやり残しタスクの一環で直してみた感じです.
r251 にて修正.

Issue 236: Windows 8.1 update1 環境で Explorer がクラッシュすることがある

https://code.google.com/p/mozc/issues/detail?id=236
安定性の確認もかねて,家でときどき tsf-mozc を使っていたところ,Windows 8.1 update1 環境で Explorer がクラッシュする確実な手順を発見したので,デバッグ・対処してみたというものです.再現率 100% なので, Visual Studio で簡単にデバッグできるかと思いきや,windbg で WinRT の内部まで潜る必要があって大変でした.Ntdebugging Blog の記事を見つけられなければデバッグは無理だったように思います.WinRT で言語共通の実行時例外がどのように実装されているか興味がある人には,この記事面白いかもしれません.
r252 にて対処.

Issue 222: Linux デスクトップ, NaCl, および Android ビルドで Ninja を使用する

https://code.google.com/p/mozc/issues/detail?id=222
2012年他にやったこと - Mozc 編 - NyaRuRuが地球にいたころ で書いたように,2012 年末から Windows 版 Mozc は Ninja でビルドされるようになっていたのですが,これを Linux デスクトップ, NaCl, および Android ビルドでも使うようにしたというものです.
やってみて分かったのですが,どうも GYP の Ninja ジェネレータとmake_global_settings という GYP の設定の組み合わせで Android 向けクロスビルドを行ったのは Mozc が最初らしく,結局自分で GYP 側も直す必要がありました.以下のようにせっせと GYP 側にパッチを送っていたりします.たまに役立つ GYP コミット権.

Mozc 側では,r265, r266, r267, r268 の 4 回に分けて有効化されました.

Issue 240: Linux デスクトップ, NaCl, および Android ビルド環境構築のための Dockerfile の提供開始

https://code.google.com/p/mozc/issues/detail?id=240
OSS Mozc の公式ビルド環境を Docker で定義してみよう,と Docker の勉強がてらやってみました.特に Android 版 Mozcのビルド環境を Docker 化できたおかげで,その後のビルドの検証がだいぶ楽になりました.
r271 にて Ubuntu 12.04 ベースのコンテナを提供開始.
r330 にて Ubuntu 14.04 ベースのコンテナの試験提供を開始しました.
なお, r486 で Ubuntu 12.04 ベースのコンテナは役目を終了し,以後は Ubuntu 14.04 に一本化しています.

Ninja の Console Pool 機能を有効化

https://code.google.com/p/mozc/source/detail?r=314
Ninja 1.5 の新機能に Console Pool というのがあって,これが Mozc の Android ビルドのときに使えるかなと試してみたものです.
r314 にて有効化.

Issue 246: Linux, NaCl, および Android 環境で,'Set input mode to X' コマンドが直接入力モードで動作しない

https://code.google.com/p/mozc/issues/detail?id=246
https://bugzilla.redhat.com/show_bug.cgi?id=1119048
Red Hat Bugzilla で ibus-mozc 向けに報告されていた問題で,キーマップエディタで直接入力モード時に「ひらがなモードに切り替え」というコマンドを設定したとしても,実際には動作しないという問題です.
Windows では問題なく動作するのですが,動作するプラットフォームが実はホワイトリスト方式になっていて,Windows 以外では意図的に受け付けないようになっていました.実際には Mac 以外で許可することに問題はないので,そのように対処した,というものです.
r315 にて修正.

Issue 244: Visual C++ 2013 対応

https://code.google.com/p/mozc/issues/detail?id=244
OSS Mozc を Visual Studio 2013 でビルドできるようにしてみた,というものです.
r311 以降で対応.
ちなみにこの後 Visual Studio Community がリリースされたおかげで,無償版の Visual Studio を利用して OSS Mozc Windows 版をビルドできるようになっています.
なお, r509 以降では Visual Studio 2013 にサポートするコンパイラを一本化しています.

About ダイアログ上の Copyright 表記の更新

2014 年も半分以上過ぎているのに,About Dialog 上の Copyright 表記が 2013 年のままだったので直したというものです.
r324 にて修正.

アプリケーションマニフェストに Windows 10 対応宣言を追加

Windows 版の実行ファイル全てに,Windows 10 対応という GUID をセットするというものです.
r344 にて対処.

UserHistoryStorage の 2038 年問題

Mozc の入力履歴ファイル内で使用されるタイムスタンプが 2038 年以降不正な値になるという問題です.たまたまコードを眺めていて発見したので直してみました.修正自体はほぼ Protocol Buffers で定義されたデータ型の型を uint32 から uint64 に変更するだけといえます.Protocol Buffers の特徴として,この変更に関しては互換性と保存されるデータサイズ的に影響を及ぼさないと分かっていたため,割と気軽に直すことができました.
r346 にて対処.

SafeStrToDouble/SafeStrToFloat を Android 環境で再度有効化

https://code.google.com/p/mozc/source/detail?r=370
特定バージョンの Android NDK でビルドすると,strtod に 16 進数表記の文字列を渡したときの挙動に非互換の変更があったため,Android 環境で一部のテストを無効化されていたのですが,Android NDK r10c でこの問題が修正されたためテストを再度有効化したというものです.
r370 にて対処.

Issue 249: Windows 8 以降の環境でスタート画面に統合された IME 候補表示が正しく動作しない

https://code.google.com/p/mozc/issues/detail?id=249
これも家で tsf-mozc を使っていて発見した問題です.当時実験的に tsf-mozc でITfFnSearchCandidateProvider をサポートしていたのですが,どうも実装が不完全だったため逆に問題のある挙動になってしまっていたらしく,該当機能を無効化することで対処となりました.
r329 にて修正.

Issue 248: Android 環境に物理キーボードを接続し,デッドキーを使用するとクラッシュする

https://code.google.com/p/mozc/issues/detail?id=248
OSS Mozc の Issue Tracker に寄せられた報告です.Android 環境での Mozc のデッドキーイベントの扱いに問題があり,特定キーボードレイアウトで特定キーシーケンスを入力すると必ず Mozc がクラッシュしてしまっていました.
r463 にて修正.

Issue 255: ibus-mozc を XIM プロトコルで使用すると,フォーカスロスト時にプリエディットがクリアされない

https://code.google.com/p/mozc/issues/detail?id=255
OSS Mozc の Issue Tracker に寄せられた報告です.元々は日本 IBM の人から SUSE Linux に報告が寄せられ,さらに openSUSE の Issue Tracker を経ての報告だそうです.
ibus-mozc を XIM プロトコルで使用し,gedit 上でプリエディットが残っている状態で,別アプリケーションにフォーカスを移し,再度 gedit にフォーカスを戻すと,以前のプリエディットは既に確定されているのに,ibus-mozc は以前のプリエディットの内容が残ったままであるかのように振る舞う,という問題でした.
調べてみると Mozc 1.2.809.102 より前のバージョンではこのケースでも正しく動いていたものの, Chromium OS (当時はまだ IBus を使用していた)でのフォーカス周りのバグに対処すべくあれこれ変更するうちに現在の挙動になってしまったということが分かりました.
r386 にて修正.

2015 年の話とか

今年は休日の空き時間があまり読めないので,分からないというのが正直なところです.少なくとも OSS 化作業については,Android 5.0 向けの OSS 化が一息ついたので,多少は時間が稼げたんではなかろうかと.

もし何かひとつ優先するすれば,今年こそ不要コードの削除に注力したいかなと.Windows XP 対応のために入れていたコードや,もうあまり意味の無さそうなパフォーマンスチューニング,古いコンパイラのために必要だったコードなどなど.書いた本人ならすぐに判断がつくものの,もし仮に誰かが引き継いでしまうと消していいかの判断がつかなくなりそうなものは今のうちに消してしまいたいと思う次第.

次点としては,Windows 版向けにずっとやりたかったことあれこれ,たとえば OSS 版のインストーラーを動くようにするとか per-monitor DPI-aware 対応とか.とはいえ,すべては今年の休日具合次第なんですけどね.あとどんなミートアップに参加するかも.

2014 年やったこと - Chromium 編

Chromium 関係,コードはあまり書いていない一年でした.数字としてはコミット 17 回,コードレビュー 13 回となります.一方 Issue Tracker 上での活動はまあそれなりにで,Issue 登録 19 回,コメントを書き込んだり状態を変更したりといった Issue の数は 173 件となりました.

自分でやったこと

Windows 版 Chromium の Aura 移行完了後不要になった IME 関連コードの削除

2014 年 1 月から 2 月にかけて,Aura + Ash 移行に伴い不要になった IME 関連コードの削除をやっていました.既にチームを移っていたこともあり,最後の後片付けという感じです.書くだけ書いて後片付けを次の人に任せるのは気が引けたというのもありますし,Chromium に関わり始めた 2012 年ごろ,おっかなびっくり書いたやっつけコードを自らの手で消し去る良い機会だったというのもあります.

Windows 版 Chromium で HiDPI 有効化時に IME の候補ウィンドウの位置がずれる問題の修正

f:id:NyaRuRu:20150104153618p:plain
2013 年に自分が登録したバグですが,普段使っている Windows ノート PC がこの影響を受けていてあまりにも不便だったので,土日の空き時間を使って自分で直してみたものです.

Android 5.0 の新 IME API への対応 (未完)

後で改めて書くかと思いますが,2014 年のメインプロジェクトの仕事のひとつで Android 5.0 にいくつか IME API を追加,というのをやっておりました.追加した API は InputMethodManager.html#updateCursor API の置き換えを狙ったもので,変換中の文字列のスクリーン座標を IME に伝えられるようにするものです.さて,この種の新 API の宿命として,IME だけでなくテキスト入力 widget 側にも新 API 対応が必要になります.Android の標準 TextView を使っているアプリケーションであれば何もしなくてよいのですが,独自にテキスト描画を行っているアプリケーションではそうはいきません.Chromium も当然ながらこのカテゴリに属し,Android 5.0 の SDK を使用して割と面倒なコードを追加する必要がありました.この仕事,当初は Chromium Android 版チームの人に頼もうかと漠然と考えていたのですが,Android 5.0 の開発期間中にとりあえず動く検証用コードを書き上げてしまったこともあり,なし崩し的に自分で対応コードを追加することになったという感じです.
とはいえコードレビューでのリクエストに色々応えていたらだいぶ規模が大きくなり,そのまま年末休暇シーズンに突入してしまったため,実現は 2015 年に持ち越しとなりました.

Issue Tracker 上で情報提供しつつ直してもらったものからいくつか

base::SHA1HashBytes に 4 GB 以上のデータを渡すと誤ったハッシュ値を返す

別件で SHA1 Hash の計算式を調べていたときに,参考に見てみた Chromium の内部関数に問題を見つけてしまったというものです.ざっと見てみた範囲では問題になるケースは見つけられませんでしたが,念のためセキュリティバグとして報告,修正してもらいました.

Chrome Remote Desktop (Chromoting) のウィンドウ上ではクライアント環境の IME は常にオフにすべき

仕事で Chrome Remote Desktop (内部コードネーム Chromoting) を使うことが良くあるのですが,IME 関係で不満があったので変更を提案して対応してもらったというものです.
Windows 環境から Linux 環境に Chrome Remote Desktop で接続するケースを考えてみます.このとき,Chrome Remote Desktop のウィンドウ上でクライアント側 (Windows 側) の IME を有効にすることができてしまっていました.しかし,確定した文字列は単に捨てられてしまうという状況でした.
f:id:NyaRuRu:20150427054704p:plain
これは,特に機能として役立っていないだけではなく,パスワードを意図せず IME に学習させてしまうなど望ましくない動作でした.そこで,クライアント側の IME を完全にオフにするという提案をしてみた次第です.
以前 Pepper SDK の IME 周りを担当されていた kinaba@ さんの的確なアドバイスのおかげで,数日のうちに対応していただくことができました.

Time::EnableHighResolutionTimer(false) が無視されることがある






2014 年 7 月頃,ネットメディアを中心に Windows 版 Chrome の電力消費量が話題になったことがありました.報道に関して色々言いたいことはあったものの,実際うまく動作しないケースが存在することも知っていたので,念のため知っていることを Issue Tracker にコメントしておきました.実際には他にも色々問題があったらしく,しばらくしてまとめて修正していただきました.マルチスレッド絡みバグがたくさん見つかったそうです.

base::PowerMonitor::AddObserver と base::PowerMonitor::RemoveObserver が同じスレッドで呼ばれないことによる use-after-free

たまたま自分の目の前で Google Chrome Canary がクラッシュしたので,クラッシュダンプを頼りに Issue Tracker 上で原因究明に協力したものです.Issue としては以下のものになります.(Chromium ではクラッシュバグは原則セキュリティバグ扱いのため,特定アカウントでログインしていない限り以下のページは表示されません.面倒な仕組みですみません)

仮想関数呼び出しで EXCEPTION_ACCESS_VIOLATION_EXEC という一見不可思議なクラッシュだったのですが,イベントリスナーとして使われている仮想関数だったことから use-after-free を疑って関連コードを読んでいくと,以下のようなコードを発見.これ,AddObserver と RemoveObserver が同じスレッドで呼ばれないと,RemoveObserver はサイレントに何もしないという実装だったという.
http://src.chromium.org/viewvc/chrome/trunk/src/base/observer_list_threadsafe.h?revision=212281#l125

  // Remove an observer from the list if it is in the list.
  // If there are pending notifications in-transit to the observer, they will
  // be aborted.
  // If the observer to be removed is in the list, RemoveObserver MUST
  // be called from the same thread which called AddObserver.
  void RemoveObserver(ObserverType* obs) {

「このせいじゃね?」とコメントしておいたところ,直前にコミットされていた Issue 482333003: Reland r290125: Close all active PeerConnections upon OS suspend - Code Review がまさにこの問題を踏んでいたことを特定していただきました.これにコードレビューで気付けというのはちょっと無理ですかね.ライブラリの設計ミスとでも言うべきか.

Google Cloud Messaging クライアントの exponential backoff が約 7 日でオーバーフローする

利用者からバグ報告に担当者が割り当てられていなかったので,関係しそうな人々を CC しただけ仕事です.Chrome が特定条件で (Google のサーバーの?) port 5228 に大量のパケットを送信する,という問題.原因だけは気になっていたのでその後もやりとりをウォッチしていたのですが,exponential backoff の計算方法に問題があって,約 7 日送信失敗し続けると double の精度を超えてしまっていたというオチでした.

Windows 10 Technical Preview 上で動く Chromium の User Agent が "Windows NT 6.3" と返す

Windows 8.1 のときにもあった,GetVersionEx 嘘つき問題です.マニフェストファイルに GUID を追加すれば解決する話ではあるのですが,実際手を出してみたら大量の yak shaving に付き合わされたのが前回.今回は Issue として登録して直してもらいました.

感想

何となくそうではないかと予想していましたが,Issue Tracker で人に作業を振ったりあれこれ意見を述べるだけってのはだいぶ楽ですねこれ.チームを移ったこともあり,基本的に休日や通勤中の空き時間でできる作業に専念していたのですが,これぐらいの作業なら空き時間との相談で続けてもいいかなというところです.
一方で Chromium のコードを書く人の負担が半端なく大きいという問題は未解決のままと言えます.かなり厳しいコードレビュー,法外に高いクロスプラットフォーム対応コスト,不安定なテスト,コミット後忘れた頃にやってくる regression とクラッシュの報告.実際に問題を修正してくれている人たちは何故そんな面倒な仕事をしてくれているのか,そこをうまくシステム化していかないとブラウザ開発というプロジェクト自体の持続可能性が危ういんではないかなと.Issue Tracker であれこれ意見を言うだけの人が 100 人いても,大変めんどくさいコード変更作業に立ち上がってくれる人が 1 人現れない限り問題は解決しないという構造をプロジェクト運営にどう反映させていくのか.思うことは色々あるのですが,この辺についてはまた改めて別のエントリで書いてみたいと思います.
2015 年ですが,Android 5.0 の新 IME API 対応はさっさと終わせないとですね.その先はあんまり考えていないです.まあなるようになる感じで.

References

コメントした Issue 一覧

2013年やったこと - Firefox, LibreOffice, Gyp 編

2013年やったこと - Firefox, LibreOffice, Gyp 編.

Firefox: TSF 無効時の HTML5 Forms のtype 指定と InputScope の対応作業

866736 – InputScope support for IMM32 with CUAS
f:id:NyaRuRu:20140105122321p:plain
TSF モードの Firefox は InputScope に対応既なのですが,IMM32 モードでは特に何も考慮されていませんでした.実は SetInputScopes API を使えばレガシー IMM32 アプリでも InputScope に対応できるので,Firefox でもやってみた,という変更です.以下のように InputScope 周りを重点的に見ていた時期だったので,タイミングが良かったというのもありました.

  • 同様の方法で InputScope に対応するコードを Chromium 向けに書いていた
  • 同時期に Mozc の InputScope 対応を実装していた

ゴールデンウィークに Mozilla Japan の六本木オフィスで行われたハッカソンで書き上げたものです.パッチの送り方も一緒に教えてもらえたので大変助かりました.
ビルドを壊しちゃったり コードに CRLF が混ざっていたりと,チェックイン後も色々ご迷惑をおかけすることに.中野さんをはじめフォローしていただいた Mozilla の皆様ありがとうございました.

LibreOffice: IMR_QUERYCHARPOSITION 対応

Bug 64298 – Support IMR_QUERYCHARPOSITION for better integration with IMEs
LibreOffice に IMR_QUERYCHARPOSITION サポートを追加して,各種 IME が適切な位置にサジェストウィンドウを表示できるようにするというパッチです.

LibreOffice Writer 4.0 (修正前)

f:id:NyaRuRu:20140105122333p:plain

LibreOffice Writer 4.1 (修正後)

f:id:NyaRuRu:20140105122401p:plain
この問題との付き合いは長く,Mozc リリース直後にOpenOffice.org でサジェストウィンドウの表示位置が変というレポートがたくさん来て以来となります.3 年半のときを経てやっと根本的に対処できました.

IMR_QUERYCHARPOSITION を (直接的または間接的に) 利用している全 IME で表示位置が改善されます.
このパッチもたしかゴールデンウィークを利用して書いています.パッチの送り方からチェックイン後のビルド失敗の後始末まで LibreOffice チームのみなさんにたくさんフォローしていただきました.この場をお借りして改めてお礼申しあげます.

Gyp Issue 385: ninja generator doesn't handle LinkTimeCodeGeneration/ProfileGuidedDatabase for PGO

Issue 385 - gyp - ninja generator doesn't handle LinkTimeCodeGeneration/ProfileGuidedDatabase for PGO - Generate Your Projects - Google Project Hosting
年末休暇で暇つぶしに適当に見つけた Gyp のバグを直してみたというもの.特に困ってはいなかったのですが,たまには Gyp コミッターらしいことでもしておくかという感じ.
Gyp の Ninja 向けジェネレータが PGO 設定の一部をサポートしていなかったので,対応してみました.実装自体は単に対応表を書くだけだったのですが,コードレビューで「実際に PGO ビルドするテストもあるといいね」という流れになってテストを書いてみたら予想を上回る大変さだったという.この体験がもとになって書いたのが次の記事です.

Chromium で PGO が有効化される場合はもしかしたら意味があるかもしれません.もっとも,そのまえに Chromium のビルドシステムが Gyp から GN (Generate Ninja) に移行しちゃうかもしれませんが.

Gyp Issue 389: ninja generator is not aware of VCLinkerTool.GenerateManifest

Issue 389 - gyp - ninja generator is not aware of VCLinkerTool.GenerateManifest - Generate Your Projects - Google Project Hosting
上の Gyp Issue 385 のテストを書くのにどうしても必要になった機能です.

感想

メインプロジェクトと関連があるところを攻めつつ,みんなが便利になる方向の改良にもっていけたように思います.

今年の話とか

今年も隙を見て OSS プロジェクトにパッチを投げていきたいです.

2013年やったこと - Chromium 編

2013年やったこと - Chromium 編,いつもどおりコードが公開されている範囲で.

HTML5 Forms の type 指定と InputScope の対応作業

HTML5 Forms の type 指定と Windows 環境での InputScope を連携させるという変更です.Windows 8 以降のオンスクリーンキーボードは InputScope でレイアウトを切り替えるため,IME を必要としない言語でも有効なのがポイントです.
変更自体はそれほど難しくなく,特にトラブルもないまま無事リリースされていきました.
f:id:NyaRuRu:20140104120453p:plain
メジャーブラウザの InputScope 対応状況については,以下の記事にまとめています.

Omnibox フォント問題

Omnibox のフォントサイズが微妙に変わった/特定文字が□になったから直すべし,という案件.

f:id:NyaRuRu:20140104120504p:plain
なんでこのバグを担当する羽目になったかというと,Omnibox で使っていた RichEdit のバージョンを上げたのが私だったからです.なぜバージョンを上げる必要があったかというと,Immersive Mode で IME をサポートするためには TSF を使う必要があって,古い RichEdit は TSF をサポートしていなかったからです.欲しかったのは TSF の機能だけで,その他は前と同じように動いてほしかったのですが,そうはいかなかったようでした.
問題自体は i18n 対応でありふれたものです.様々な経験則を駆使しつつ複数フォントで文字をレンダリングしたいという需要があるのですが,それはそれでうまく動かないことがあるというもの.RichEdit ではいくつか制御フラグがあるものの (e.g., IMF_DUALFONT, IMF_AUTOFONT) あちらを立てればこちらが立たずで,直ったーと思ってリリースしてみると予想外の言語から「見た目が変わったー」「文字が□になるー」とバグが報告されるという.
ちなみに後で分かったのですが,Aura という UI フレームワークへの移行することがだいぶ前に決まっていて,その過程で RichEdit は使われなくなるとことも決定事項だったのでした.実際,このとき書いたコードは 1 年と経たず使われなくなりました.

破損 msftedit.dll 問題

これも同じく RichEdit のバージョンを上げたら壊れた問題です.RichEdit 4.1 は msftedit.dll という DLL で提供されているのですが,どうも世の中には msftedit.dll が破損している環境というのが一定数あるらしく,その場合は Omnibox が真っ黒になって何も文字が入力できなくなってしまうとのこと.ユーザーフォーラムに多数問い合わせが来て発覚.議論の末 LoadLibrary("msftedit.dll") が失敗したらダイアログを出してヘルプページに誘導する,ということになりました.ダイアログを表示するような UI 周りのコードを書くのは初めてだったのでだいぶ苦労したのを憶えています.がその後数か月のうちに RichEdit は使われなくなるわけで,このコードも今は使われていません.(たぶんもう消されてるはず)

用語整理

以後のトピックを理解する上で必要になる用語整理をここで軽く済ませておきます.

Aura

上の資料はだいぶ古くなっているのではないかと思いますがかといってあんまりいい資料もないかも.
一般的には「全てを GPU でレンダリングするための変更」という説明をされることが多い Aura ですが,何年もかけて移行していると関係者が増えすぎていて一言でいうのはもう無理なのではという印象もあります.
Chrome OS はとっくの昔に Aura に移行していて,現在 Windows と Linux で移行の最終段階です.Windows については次の Chrome M-32 で安定板にも Aura が投入されそうではありますが正式にリリースはまだなのでご注意を.
基本的には Chrome OS / Linux / Windows で共有されるコードが増える方向のプロジェクトです.ウィンドウを抽象化し,UI イベントを抽象化し,IME 処理も抽象化し,レンダリング API も抽象化し,という流れ.イベント処理周りのコールスタックは数割深くなる印象.また,いわゆる抽象化の漏れによるバグの比率が増えます.
一方,UI イベントの型と描画 API がプラットフォーム間で統一されることから,新規プラットフォームへの移植時に必要なコード量は確実に少なくなります.WebView として Chromium を使いたい人は特にこの点を重視している模様.たとえば Intel の人たちが中心になって行っている Chromium 派生プロジェクト Crosswalk は Aura が前提だったりします.
ちなみに,Aura は大規模にコードを入れ替えるため,ビルドオプションで有効無効を切り替えます.実行時スイッチで切り替えられず,安定性・互換性・パフォーマンスに関する影響は非常に大きいということで,リリースエンジニアリングの観点では非常に厄介なタイプの大規模変更です.

Ash (Aura Shell)

Aura のウィンドウモデルやイベントモデルの上に作られたウィンドウマネージャで,Chrome OS のために作られました.実際 Chrome OS では,はるか昔に投入済みです.
経緯は知りませんが Windows 8 以降の Immsersive Mode でも使われることになり,すでに開発版等ではリリースされています.Windows での実装は,ブラウザプロセス + レンダラプロセスで構成されている Chromium にさらに WinRT プロセスが追加されるという非常にトリッキーなマルチプロセス構成となっており,IME サポートもだいぶ面倒なことになりました.詳しくは後ほど.
後になって知ったのですが,Immsersive モードに Ash を投入するという構想自体はだいぶ前からあったようです.Windows 版での Ash モードで IME が動かないというバグ登録されたのは,2012 年12月7日のことでした.
Issue 164964 - chromium - Support basic functionality of IMEs in Chrome Metro ASH on Windows 8 - An open-source project to help move the web forward. - Google Project Hosting

Desktop Aura

ウィンドウマネージャは従来通り OS のものを使いつつ,独立した各ウィンドウの内側だけを Aura に置き換えるというもの.デスクトップモードの Windows と Linux で投入されます.現在の Chrome OS にこの動作形態はありません.
IME に関していえばウィンドウマネージャは OS のものを使うという点がやっぱり罠で,Ash 環境では表面化しなかったタイミングバグやイベント順序バグが沢山表面化することとなりました.
先ほど述べたように,予定通りいけば次の Chrome M-32 安定板からWindows 版でも有効化される見込みとなっています.IME に限らず,アクセシビリティ系ツールとの互換性,パフォーマンス,GPU ドライバとの互換性等,あちこち大変みたいです.

Native Textfield Views

従来ネイティブウィンドウを使って実装していたテキストフィールドを自前の Window-less コントロールに置き換えようというものです.なお Web コンテンツ領域はもともと自前のテキストフィールドだったので,ここで置き換えられるのは Omnibox 等外側の部分のテキストフィールドのみなことに注意.
Desktop Aura ではトップレベルウィンドウ以外はネイティブウィンドウを持たないので, Aura 移行のためには必須となります.Aura 移行が完了している Chrome OS 環境では当然のことながら置き換え済みです.
Native Textfield Views の実装自体は Aura から独立していて,Windows 版 Aura 投入にあたっては以下のように段階的な移行が行われました.

  1. non-Aura + Native Textfield Views 無効
  2. non-Aura + Native Textfield Views 有効
  3. Aura + Native Textfield Views 有効

このいずれの構成でも IME は動作する必要があり,移行期には考慮すべきケースが増えてしまうので大変でした.
Aura と異なり,起動時のフラグで有効無効を切り替えるという特徴があります.上の 1 と 2 は同じバイナリで提供することが可能です.

構成

一時的にテストされたものも含むと,IME に関しては以下の 7 パターンの構成を見ることになりました.

  • IMM32 で動作する non-Aura モード (Windows XP, Vista, 7, 8, 8.1 のデスクトップモード)
    • Native Textfield Views 有効の場合
    • Native Textfield Views 無効の場合
  • TSFで動作する non-Aura モード (Windows 8, 8.1 の Immersive モード)
    • Native Textfield Views 有効の場合
    • Native Textfield Views 無効の場合
  • IMM32 で動作する Desktop-Aura モード (Windows XP, Vista, 7)
  • TSF で動作する Desktop-Aura モード (Windows 8, 8.1) [のちに不要だったことが判明]
  • TSFで動作する Ash モード (Windows 8, 8.1 の Immersive モード)

不運だったのは,この「TSF で動作する Desktop-Aura モード (Windows 8, 8.1)」が,非常に不安定だった割に,実際のところ全く不要だったとあとになって判明することです. 2013 年 4 月ごろ,将来の必要性を見越して Windows 向け Aura/Ash を担当しているチームから追加されたこのモードは,その後 5 か月にわたって安定化のためにエンジニアリングリソースを吸い込み続け,そして一度も安定板に投入されることなく削除されることになります.
なお,移行が概ね完了した Chromium M-33 以降では,以下の 2 つの構成にまで簡略化されています.

  • IMM32 で動作する Desktop-Aura モード (Windows XP, Vista, 7, 8, 8.1 のデスクトップモード)
  • TSFで動作する Ash モード (Windows 8, 8.1 の Immersive モード)

というわけで用語説明終わりです.

Native Textfield Views が IMR_QUERYCHARPOSITION をサポートしない

ゴールデンウィークの暇つぶしに軽い気持ちでパッチを投げたもの.Desktop Aura や Ash のリリースプランを当時の私は全く知らなくて,村の近くに出没したゴブリンでも退治する感じでパッチを投げてしまったのでした.ちなみにこの後どうなるかというと,

  1. Native Textfield Views 導入編 (ゴブリンの巣)
  2. Native Textfield Views + non-Aura完結編 (なんか最初の山場)
  3. Desktop Aura 編 (魔王城)
  4. Ash 編 (宇宙)

みたいな流れになります.
この時直したバグは,Native Textfield Views が有効だとサジェストウィンドウ位置が揃わないというものでした.
f:id:NyaRuRu:20140104120522p:plain

このパッチのせいで,Omnibox 上でカーソルが点滅しなくなるというバグを引き起こしてしまいあわてて直したりもしています.

Native Textfield Views で変換中の文節区切りが分からない

ゴールデンウィークのゴブリン退治その 2.
IME で入力中,文節区切りが分からないという問題の修正です.Chrome OS 版で発見されたまま直っていなかった問題ですが,さすがにこのまま Windows 版に出すのはまずかろうという気持ちで (特に担当だったわけではないのですが) 直してみました.

Native Textfield Views + non-Aura 環境での IME 対応

6 月ごろだった気もしますがやや記憶があいまい.

思っていたより事態が深刻だと気付き始めます.というのも,既に Aura への移行を済ませてしまっている Chrome OS と違い,Windows 版では Native Textfield Views と non-Aura という組み合わせでも IME を完璧に動作させる必要があります.もちろん最終的には Windows での Aura ビルドでも IME サポートを行う必要もあります.ともあれ,Native Textfield Viewsの投入は近そうだということで,何とか 7 月末に Native Textfield Views + non-Aura という環境で概ね問題なく動くところまでこぎつけました.
コードだけでなく Design Doc もこのタイミングで書くことにもなりました.しかし既に Aura への移行を済ませた Chrome OS の IME 実装を大きく変えるわけにもいきません,必然的に Design Doc の大半は,既にコミットされているコードの説明に費やされました.

Desktop Aura 対応

8 月から 9 月にかけて.Native Textfield Views がひと段落したと思ったのもつかの間,すぐにも Desktop Aura が Windows に投入されそうと知らされ再び戦場へ.
ウィンドウのフォーカス処理を OS に任せるか Aura で行うかの違いは思った以上に大きく,Chrome OS では表面化しなかったイベント順序問題やタイミングバグが頻発します.
さらに IMM32 と TSF という異なる実装を同時に安定化させる作業は困難を極めました.また,コードが共有される関係上,変更が Chrome OS での IME サポートを壊すリスクもあり,頻繁にマニュアルテストを行う必要がありました.特定の操作でのみ発生する use-after-free バグが頻発し,GC が恋しくなった時期でもあります.
そして9 月中旬.ラスボスだと思っていたものがラスボスではなかったことを知ります.Immsersive モードで Ash を使用する予定であること,およびそれが WinRT プロセスとデスクトッププロセスが入り混じったものになることを初めて把握します.と同時に,4 月に Windows 版 Ash チームの人がデスクトップモードでも TSF を有効化したその意図をやっと理解したのでした.ここにきての伏線回収.結局それは Ash モードでの動作を見越して投機的に行われたものだったのでした.
が,改めて実験してみると,TSF ランタイムに嫌な制約があって,ブラウザプロセスで TSF を有効にしても Ash モードでの IME サポートの役には立たないことが明らかに.伏線を回収してみたら事件の真相は単にコミュニケーション不足だったという.人類はなんど同じネタを繰り返さなければならないのでしょうか.
最終的には,デスクトップモードでの TSF サポートをやめたことで,IME に関する Desktop Aura の安定化が急激に進むのでした.

Ash での IME サポート

Windows 環境での Ash モードは非常に特殊な動きをします.Win32 的なキーボードフォーカスは WinRT プロセスが保持する一方,UI イベントの処理は全てデスクトップで動いているブラウザプロセスに転送するという動作をします (ウェブコンテンツはさらにレンダラプロセスでレンダリングされます).キーイベントに IME が絡まなければ,基本的にこれはうまく動作します.
さて IME ですが,WinRT プロセス内で IME を動かすか,ブラウザプロセス内で IME を動かすかという選択肢が考えられます.しかし,いくつかの実験から分かったのは,TSF ランタイムはフォアグラウンドプロセスかどうかを監視していて,バックグラウンドプロセスで動く IME は決してフォーカスイベントを受け取らない,という事実でした.消去法的に,WinRT プロセス内に TSF ベースのテキストストアを構築し,IPC を通じてブラウザプロセス内の既存コードと連携させる以外道がないということになります.このことをちゃんと調べられたのが 2013 年 9 月,Ash モードでの IME サポートに関するバグが登録されて実に 10 か月が経過してのことでした.
Issue 164964 - chromium - Support basic functionality of IMEs in Chrome Metro ASH on Windows 8 - An open-source project to help move the web forward. - Google Project Hosting
動作する最低限のコードは 10 月中には完成させたのですが,Desktop Aura の安定化を優先させたこと,変更が大きなことから,実際にチェックイン作業を始められたのは 11 月下旬でした.残念ながら M-32 には間に合わず,M-32 ベースの Ash モードでは IME は動作しません.
とはいえ遅れはしたものの,予定した変更の大半は無事 M-33 ブランチカット前にチェックインを終えることができました.M-34現在,Ashモードでニコニコ動画にコメント入力すら可能です.ニコニコ動画に IME で入力するときは,

  1. WinRT プロセス
  2. ブラウザプロセス
  3. レンダラプロセス
  4. プラグインコンテナプロセス

と 4 つのプロセスが非同期 IPC で連携しながら動作することになるのですが,その綱渡り具合を真に理解できる人がはたして世界に何人いるのかにも興味があるところ.
f:id:NyaRuRu:20140104120535p:plain
Ash モードの Flash 上で候補ウィンドウの位置が正しく設定されない問題は M-34 で直ります.

IME に関係なく行きがかり上担当することになったもの

User Agent 文字列が正しい OS バージョンを示さない

Windows 8.1 で動く Chromium の User Agent 文字列が

Mozilla/5.0 (Windows NT 6.2; WOW64)

みたいになっていました.いわゆる GetVersion(Ex) API 問題.ブラウザの User Agent 文字列から OS のシェア推定とかやっているところには影響があったはず.


という感じの,のどかな会話から思いがけず発覚したのが印象的でした.Mozilla 側でも直後に修正されています.
まあついでということで Chromium にはパッチを投げてみました.メジャーブラウザ の User Agent 文字列に影響する変更というのはなかなか貴重な体験でした.

全部の *.exe にアプリケーションマニフェストファイルを設定すべき

先ほどの GetVersion(Ex) API 問題を修正するために chrome.exe のマニフェストファイルをアップデートしたわけですが,コードレビューで「ユニットテストとプロダクションバイナリで動作が変わるのは良くないから,全部の *.exe に同じアプリケーションマニフェストを設定できないかな」的なコメントが付いたで急遽やることになった仕事です.
Mozc で培った Gyp 誰得ノウハウをいかんなく発揮して華麗に解決! といけばよかったのですが,実際全部の *.exe にマニフェストファイルを埋め込んでみたら今度は「ユニットテストのビルドが 30 倍遅くなった!」と怒られます.えー

結局いくつかのユニットテストではマニフェストファイルを埋め込まず隣に置く方式になりました.
後日談としては,Gyp r1813 でマニフェストファイルの扱いが大幅に見直されたので,パフォーマンスを悪化させずにマニフェストを埋め込むことができるようになりそうです.

Web MIDI: Windows 対応

社内 TechTalk で Toyoshima さんが Windows 版 Chromium 向け Web MIDI の実装者を募集していて,Mac 版が既に動いているならそれほど苦労せずに Windows 版も対応できるかなと思って手を上げました.
実際,10 月19日の Web Music ハッカソンにふらっと立ち寄って半日で単純な入出力までできたのですが,細かいところまで見てみると既にある共通部分のコードにも色々バグを見つけてしまい,基本部分からひとつずつバグを直していくことになりました.私にとっては Blink への初コミットが Web MIDI となります.
Aura/Ash の IME 実装がほんとに大変な時期だったというのもあって,コードレビューの待ち時間等を利用して少しずつ直しながら,一か月半後ぐらいの12月4日にやっと有効化となりました.

Windows で電源イベントのオブザーバーが動いていなかった

Web MIDI 関連で調べているときにたまたま見つけてしまったバグです.Chromium の実装用のライブラリに,電源コードの抜き差しや,サスペンドへの移行・復帰時にコールバックを受けられる仕組みがあるのですが,これが数か月動いていませんでした.
タイマ割り込み精度の変更とか GPU ウォッチドッグタイマの設定とかネットワークのコネクションキャッシュがこれらのイベントに依存しているみたいですが,誰も気づいていなかったみたいですね.

その他社内コンサル業

W3C Working Draft: Input Method Editor API

http://www.w3.org/TR/ime-api/
W3C に提案中の Input Method Editor API について,社内で API 設計の議論をしたり,Chromium での実装に関してアドバイスをしたりしています.

chrome.input.ime

http://developer.chrome.com/extensions/input_ime.html
Chromium OS 専用の内部 API である,chrome.input.ime についてもたまに議論に参加したりしています.

感想

色々思うところのある1年でした.

今年の話とか

2014 年 1 月から社内の (Chromium 以外の) 別プロジェクトに移ることにしました.Web MIDI 等,趣味として参加できそうな部分は引き続きコミットしていければと思いますが,締め切りのあるバグを担当するほどの余裕はたぶんないと思います.社内コンサル業と,IME 周りのクリティカルな部分のコードレビューに費やす時間ぐらいはまあなんとか捻出したいところ.

2013年やったこと - Mozc 編

2013年やったこと - Mozc 編,いつもどおりコードが公開されている範囲で.

Mozc IPC の Windows 64-bit 対応

Windows 環境で動く Mozc は名前付きパイプ使ってプロセス間通信を行うのですが,いわゆる "Squatting Attack" 対策として,GetNamedPipeServerProcessId API で接続先のプロセス ID を取得し,さらにそのプロセス ID から GetModuleFileNameExW API で実行ファイル名を取得して検証しています.が,この仕組みが今は動いていても将来的にうまく動かないことがあるかもという問題.
このころ mozc_server や mozc_renderer を 64-bit で動かす実験を行っていて気付いたわけですが, 32-bit プロセスから 64-bit プロセスに対して GetModuleFileNameExW API を呼び出すと必ず失敗するようです.仕方ないので GetProcessImageFileNameW API を使うように変更して対処.これまで mozc_server と mozc_renderer は常に 32-bit プロセスだったので問題が表面化していなかったというわけでした.

TSF-Mozc 使用時に ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0) が動作しない

IMM32 の頃には,アプリケーション側から進行中のコンポジションをキャンセルするという API が存在しました.TSF になってそういう API はなくなったのですが,CUAS はなにかしらの方法でこの IMM32 API を エミュレートする必要があります.実際これは「コンポジションの範囲を空にする」という形で行われています.ということで TSF-Mozc でも対応する必要があるというわけでした.
Excel が,インライン補完のためにこの機能に依存しているので割と重要だったりします.

TSF-Mozc で候補ウィンドウの候補をマウスで選択・決定できない問題の修正

経験上ほとんどの人はマウスで候補ウィンドウを選択しないのを知っていたので,TSF-Mozc ではマウス関係の処理を後回しにしていました.とはいえ,タッチイベントでも動作しないのはそろそろよろしくないというのもあって修正という感じです.

TSF-Mozc での InputScope のサポート

InputScope も登場後 10 年以上経っている仕組みで,いまさらという感もありますが,Windows 8 以降はだいぶ InputScopeへの移行が進んでいます.最近はブラウザの HTML Forms の種類が InputScope で通知されたり,Windows 8 以降でImmSetConversionStatus API が (デフォルト設定では) 実質廃止になったりで,IME 側も InputScope 対応が割と重要になってきています.というわけで TSF-Mozc でも対応してみました.
アプリケーション開発者様におかれましても,Windows 8 以降,ImmSetConversionStatus API は大多数のユーザー環境で期待通り動作していない可能性が高いため,この機会に InputScope に移行してしまうのが良いのではないかと思います.詳細については下記資料等を参照してください.

さて,デスクトップ IME の文脈で気を付けるべき点として,アプリケーション側からのモード変更リクエストを永続的なものとして扱うか,一時的な変更リクエストとして扱うかという問題があります.たとえば次のような順序でテキストフィールドを移動していったとしましょう.

  1. 通常のテキストフィールド
  2. パスワードフィールド
  3. 通常のテキストフィールド

多くの場合,ステップ 1 で IME がオンの状態であれば,ステップ 3 でIME がオンの状態に復帰します.つまり,パスワードフィールドで IME が自動的にオフになるのは,永続的なモード変更ではなく一時的なモード変更というわけです.
では次の場合はどうなるべきでしょうか?

  1. 通常のテキストフィールド
  2. 全角カタカナが期待されるテキストフィールド
  3. 通常のテキストフィールド

ステップ 2 で IME の入力モードが「全角カタカナ」になるのはまあ良いとします.では,ステップ 3 に移動したときにも「全角カタカナ」のままであるべきでしょうか? それともステップ 1 のときのモードに復帰すべきでしょうか.
TSF-Mozc に関しては,InputScope によるモード変更リクエストはその場限りの一時的なものであるという解釈を行いました.したがって,上記ステップ 3 ではステップ 1 のときのモードに復帰します.簡単に Design Doc を書いていますので詳細についてはそちらを参照してください.

Windows 版 Mozc での Mode Indicator のサポート

これ.
f:id:NyaRuRu:20140102115121p:plain
位置形状とフォントサイズの決定以外のほぼ全てを担当しました.個人的にはカーソル直下よりもカーソルに対して左揃えの方が良いんではないかとか,ちょっと大きすぎるんではないかとか色々思うところはあるのですが,まあデザイナーさんがそう言っているわけで案件.個人的にはもうちょっと作りこみたかったのですが,別の仕事が忙しくてあんまり専念できなかったのが心残りです.
InputScope で自動モード変更を行う以上,この種の視覚的フィードバックは必須ですね.というわけで InputScope 対応とセットで実装されました.

周辺文字列を利用した変換の (部分的) サポート

周辺文字列を利用した変換も十数年以上の歴史があるいまさら技術ではあります.前世紀の IMM32 時代から IMR_DOCUMENTFEED メッセージをサポートしたアプリケーションでは周辺文字列を利用した変換は可能でした.
さて,周辺文字列を利用した変換というのは実際のところあんまりコストパフォーマンスの良くないアプローチだと思っています.細切れ変換対策という意味では,変換エンジン側で前回コミットされた文字列を覚えておけばすむ話です.というわけで,今回注目したのは,IME をオフにして入力しがちな文字種,つまり半角英数です.
たとえば「明日1日」という文章を入力するのに,

  1. 「明日」を入力
  2. IME をオフにする
  3. 「1」を入力
  4. IME をオンにする
  5. 「日」を入力

という使い方をする人は一定いることが分かっています.以前の Mozc では,ステップ 4 実行時に内部ステートは「文頭」状態でした.というのも,IME がオフの状態で入力された内容はモニタリングしていないので,ステップ 5 で入力する内容がどういう文章に続くものなのか把握できません.今回救いたかったのはまさにこういうケースで,カーソル前にある文字種がアルファベットか数字かどうかだけを判定します.このあたりの詳細についても Design Doc を書いておきましたので詳細が気になる人はどうぞ.

imm32-mozc と tsf-mozc,ibus-mozc および mozc_server の実装を担当しました.
IMM32 版では IMR_DOCUMENTFEED を単純に使うだけですが,TSF では Transitory Extensions という拡張的な仕組みも使っていて,メモ帳や Internet Explorer にも対応しています.軽く調べてみた限り,ATOK 2013 も MS-IME 2013 も Transitory Extensions までは使っていないようなので,ちょっとしたアピールポイントではないかと思っております.
このとき書いたコードは別のオープンソースプロジェクトで使われたりもしているようです.

C++11 対応

最近社内コード規約的に C++11 が解禁されつつあるので Mozc でもいくつかコードのクリーンアップを行っています.具体的には,

  • 自前 scoped_ptr から std::unique_ptr への置き換え
  • COMPILE_ASSERT マクロの static_assert への置き換え
  • auto での書き換え
  • NULL から nullptr への置き換え

あたりをちまちまやってます.
最近は Visual C++ の C++11 適応度合が上がってきていてだいぶ楽なのですが,意外に足を引っ張るのが Mac OS X だったり.というのも,libc++ の実行時ライブラリが標準で入るようになったのが Mac OS X 10.7 以降.未だに Snow Leopard をサポートしていたりすると C++11 の大部分の機能が使えないのでした.

UCS2 しかサポートできない内部関数の削除

Mozc コードベースに残っていた UCS2 専用関数をすべて削除しました.

TSF-Mozc でタッチに最適化されたオンスクリーンキーボードを表示するように

ITfFnGetPreferredTouchKeyboardLayout を実装するだけ,というわけでもなくて Windows のオンスクリーンキーボードは U+F003 と U+F004 に特別な意味を持たせたりしているのでその対応とか.詳しくは下記資料参照のこと.

可能なら LogicalToPhysicalPointForPerMonitorDPI API を使う

Mozc の候補ウィンドウや Mode Indicator は,入力中のプロセスとは別のプロセスで動いているので,DPI 仮想化にともなう座標変換が必要になることがあります.Windows 8.1 Preview で LogicalToPhysicalPoint API が動作しなくなっていたので,新しく追加された LogicalToPhysicalPointForPerMonitorDPI API が利用可能ならそちらを使う,といった修正を行っていました.Windows の DPI 周り,ほんと落ち着かないですね.
次の資料とかが詳しいです.
http://go.microsoft.com/fwlink/?LinkID=307061

アプリケーションマニフェストに Windows 8.1 対応宣言を追加

恒例行事.

ITfLangBarItemButton::GetIcon で返すモノクロフォントを実行時に描画

Accessibility (a11y) 案件.
Windows 版の Mozc は,控えめに言っても Accessibility 関係の機能が全然足りていないのですが,珍しく対応している機能のひとつに言語バーのテキストアイコン色があります.Windows では,デスクトップテーマをハイコントラストモード(背景色黒・文字色緑) に変更すると,言語バーのテキストアイコンも緑色に切り替わります.
f:id:NyaRuRu:20140102115125p:plain
f:id:NyaRuRu:20140102115128p:plain
ではこれどうやって実装するかですが,大まかに方法は 2 つ.
A. 実行時にアイコンを描画する.この場合,テーマカラーを実行時に取得できるので文字色は問題ない.テーマが変更されるたびにアイコンを再生成する必要がある.
B. モノクロアイコンを事前に生成しておき,言語バーAPI に TF_LBI_STYLE_TEXTCOLORICON フラグを渡す.この場合,言語バー側で色変更を行ってくれる.
Mozc はながらく B の方法を使っていました.が,最近になってビットマップハンドルのリークが発生しているという報告を受けて調べてみると驚きの事実が! ITfLangBarItemButton::GetIcon にモノクロアイコンを渡すと,言語バー内部でビットマップハンドルのリークが発生するようです.というわけで方向 A で書き直しました.
この問題,原因の発見までがとにかく大変だったのが印象的でした.ハンドルリークが全然再現できず困っていたのですが,ユーザーの方からアップロードしていただいたスクリーンショットがクラシックテーマだったことが気になってテーマを変更してみたところやっと再現できた,というケースです.どういうやりとりが行われたか興味がある人は以下のスレッドをどうぞ.

TF_LBI_STYLE_TEXTCOLORICON を使用している IME 作者様はご注意ください.

Adobe Sandbox をサポート

@corvussolis さんから Adobe Flash および Adobe Acrobat Reader で使われている Sandbox の動作について教えてもらって Mozc でも対応してみたという話.これでやっと Firefox でも「保護モード」オンのままニコニコ動画にコメントを書けるようになりました.
ご協力いただいた皆様に感謝.

Windows 8 以降で MS-IME からのユーザー辞書インポートが動かなくなっていたのを修正

詳細については diff を参考にしてください.

Owner Rights SID を設定

Windows では,オブジェクト所有者に暗黙に WRITE_DAC および READ_CONTROL 権限が許可されるという仕様がありましたが,Vista 以降この挙動を拒否することが可能になりました.

というわけで Mozc でも不要な権限を削除してみたという話.追加すると権限が減る系の SID ですね.

Windows 版 Mozc のビルド時に Cygwin が不要になった

Mozc のビルドは,もともと Cygwin に依存しないように作られていました.しかし,Mozcのビルドシステムを SCons から gyp に変更したときに,gyp に引っ張られる形で Cygwin 依存が生まれてしまいました.
外部コマンドの呼び出しに時に色々気を付ければ Cygwin は不要というのは分かっていたのですが,とりあえず重要度は低いので後回しのままとなってはや数年,いい加減直すかということでやっと依存関係を解消したという話です.Windows 版 OSS Mozc のチェックアウトサイズもだいぶ減ったはず.
ちなみに Chromium も同様の問題があって,以下のようにビルド時の Cygwin 依存を取り除く努力が行われています (こっちも数年がかり).

以下 OSS Mozc 固有っぽいもの

Issue 42: get rid of absolute path in unix/ibus/gen_mozc_xml.py

https://code.google.com/p/mozc/issues/detail?id=41
ibus-mozc のバイナリとアイコンファイルのインストール先はカスタマイズできるべきだという案件.3 年ぐらい放置されていのですが (すみません) リファクタリングの一環として対応.

Issue 180: Mozc and IBus does not respect xinerama at all

https://code.google.com/p/mozc/issues/detail?id=180
マルチディスプレイ環境で mozc_renderer が画面境界の判定に失敗するというバグ.gdk_screen_get_monitor_at_point を使うことで解決.

Issue 182: mozc property titles are empty in gnome-shell

https://code.google.com/p/mozc/issues/detail?id=182
Red Hat で ibus-mozc をメンテナンスされている Tagoh さんから転送されてきたバグの対応.Gnome-Shell はいくつかの IBus メタデータに依存しているのですが,ibus-mozc がそれらを設定していないという問題.具体的には,

  • IBus 1.5 以降にのみ存在する symbol というプロパティをセットするようにする (これを行わないと現在の入力モードがアイコンとして表示されない)
  • Gnome Shell は "InputMode" という名前のプロパティに現在のモード (全角カタカナとか直接入力とか) が設定されていることを期待しているので,プロパティ名を変更

という感じです.ちなみにこのあたりの Gnome-Shell の仕様はどこにもドキュメント化されていません.自分でソースを読んで調べる必要があります.結果的に以下のような見た目になりました.
f:id:NyaRuRu:20140102115130p:plain
このパッチがあたっていないバージョンの Mozc (具体的には 1.6.1187.102 以前) では,入力中のモードは分かりませんし,入力モードを Gnome Shell から選択することもできません.

Issue 188: Add Session command for ConvertPrevPage and ConvertNextPage

https://code.google.com/p/mozc/issues/detail?id=188
Fcitx の開発者から来た機能追加リクエスト.Fcitx の候補ウィンドウには次頁・前頁に相当するボタンが存在するのですが,その動作に対応するメッセージが Mozc のプロトコルに存在しなかったという問題.たまたま Windows 8 のソフトウェアキーボード対応で似たような需要があったので合わせて対応となりました.

Issue 189: use_libprotobuf=1 does not work in Mozc 1.10.1390.102

https://code.google.com/p/mozc/issues/detail?id=189
Red Hat で ibus-mozc をメンテされている Tagoh さんから,システムにインストールされている libprotobuf を使用する場合のビルドが壊れているとの報告があったので粛々と修正.壊したのも私なのでマッチポンプ案件だったりします.

Issue 195: Candidate Popup is hidden by Cinnamon menu

https://code.google.com/p/mozc/issues/detail?id=195
Linux で Cinnamon を使っていると,mozc_renderer がメニューの後ろ側に行ってしまうという問題.修正は 2 行で済んだんですが,環境のセットアップやら調査やらで結局 1 日ぐらいかかっているという.ちなみにこのケースでは日曜日が消えました.

Issue 198: 'build_mozc.py gyp' fails when gyp r1667 or higher is used

https://code.google.com/p/mozc/issues/detail?id=198
Mozc 開発ではビルドに使用する gyp のリビジョンを固定しているのですが,OSS Mozc をビルドする人は推奨リビジョン以外の gyp と組み合わせている人もいるらしく,gyp 側の変更でビルドできなくなることがあります.
今回は,Mozc r171 以前と gyp の r1667 以降を組み合わせるとビルドに失敗するという問題でした.

Issue 199: ibus-mozc must be locked down on the Gnome Shell's locked screen

https://code.google.com/p/mozc/issues/detail?id=199
CVE-2013-4509 の ibus-mozc 側の対処を一通りやりました.
私も完全に背景を把握できているか怪しいのですが,概ね以下のような感じだと理解しております.

  1. 2013年1月4日,Gnome 3 環境で IME をオンにしたままスクリーンをロックし, Gnome-Shell のスクリーンロック解除画面からパスワードを入力してロック解除しようとすると,パスワードフィールドで IME がオンのままになっているというバグが Red Hat の Jens Petersen さんから報告される.Red Hat Bug 891835
  2. 2013年1月9日,Red Had の Ueno さんから,GTK の "input-purpose" と "input-hints" を,パスワードフィールドにセットすることで問題が回避したらよいのではという提案がなされる.Gnome Bug 691392
  3. 2013年1月10日,Red Hat の Ueno さん,ibus-gtk2 にパッチをコミット (927e9f58).この変更は IBus 1.5.3 としてリリースされる.変更は ibus-gtk2 に対するもので,IM Engine 側では何もする必要がない.具体的には,"input-purpose" が password であれば,使用している IM Engine に関わらず IM Engine が無効化される.
  4. 2013年8月14日,Red Hat の Ueno さん,ibus-gtk にパッチをコミット (3b3a7cec).この変更は IBus 1.5.4 としてリリースされる.この変更によりまたしても挙動が変わり,"input-purpose" が password のときの振る舞いは各 IM Engineの責任になる.具体的には,IM Engine が IBus 1.5.4 以降の環境で動作する場合のみ,このコミットで追加された ibus_engine_get_content_type 等の新しい API を使って IM Engine の動作を変更する必要がある.
  5. 2013年9月21日,Red Hat の Fujiwara さんから IBus 1.5.4 のリリースがアナウンスされる.この中で "Handle GTK+ input purose for gnome-shell password dialog and each engine need to implement it" として各 IM Engine 開発者向けにアナウンス.
  6. 2013年9月21日,俺のターン! IBus 1.5.4 のリリースノートを受けて OSS Mozc Issue 199 をファイル.この時点では状況を完全に把握できていませんでした.
  7. 2013年9月30日,俺のターン! やっと状況を把握.急いでOSS Mozc Issue 199 へのパッチを公開.帰宅後色々弄っていて事態の深刻さに気付き急いで書き上げた感じの奴です.翌日ほんと眠かったです.
  8. 2013年10月1日,Red Hat の Mike FABIAN さんから,IBus 1.5.4 側の問題で "input-purpose" が正しく通知されないことがある問題が報告される.Red Hat Bug 1013948 この問題はすぐに修正されるものの,対応する IBus の安定板リリースは行われておらず,各ディストリビューションでは独自にパッチを取り込んで IBus 1.5.4 として配布している.
  9. 2013年10月25日,openSUSE のコミュニティメンバーの Takeyama さんから,Novel Bug 847718 がファイルされる.
  10. 2013年11月4日,Red Hat の Kurt Seifried さんにより今回の問題に CVE-2013-4509 が付与される.

この後 IBus を採用する各種ディストリビューションで色々対応が進んだようです.

Issue 201: making initial mode customizable for ibus-mozc

https://code.google.com/p/mozc/issues/detail?id=201
1.5.2 のころまでの IBus は,各 IM Engine は「直接入力 (いわゆる IME オフ)」というステートを持たないことを前提に設計されていました.従って,ibus-mozc は起動直後にひらがなモードである必要があります.
が,IBus 1.5.3 頃から,日本語 IME に関しては各 IM Engine 内部でオンオフ状態を切り替える方向に IBus 上流の人たちは心変わりしたようです.Red Hat の Fujiwara さんが ibus-anthy の挙動変更について下で説明されています.
https://code.google.com/p/ibus/issues/detail?id=747#c31
この変更にともない,以前の使い勝手を再現するためには,ibus-mozc が起動直後に「直接入力 (いわゆる IME オフ)」になって欲しいという要望が来たのが OSS Mozc Issue 201 です.
とはいえ Mozc は IBus 1.4 もまだ対応していますから,IBus のバージョンによらずデフォルトモードを変えてしまうと今度は IBus 1.4 ユーザーが困ることになります.また,デフォルトモードを選択する UI を追加しようにも,Mozc の設定画面は imm32-mozc, tsf-mozc, ibus-mozc, uim-mozc, fcitx-mozc, emacs-mozc, Mozc for IMKit で共有されているため,IBus 1.5.3 以降専用の項目を追加するのは躊躇するところです.
とりあえず初期状態をオフにするパッチだけは公開しましたが,以後のリリースでどうするかは未定です.
https://code.google.com/p/mozc/issues/detail?id=201#c8
ちなみに,初期値を変更するぐらい簡単だろうと思われる方もいらっしゃるかもしれません.が,実際は結構面倒です.事実,上のパッチを適用すると,mozc_server が予想外のクラッシュをした時のプレイバックの仕組みが壊れることが分かっています.初期値が変わっちゃったわけですね.

その他 OSS Mozc 関係でやったこと

2013年4月ぐらいに fcitx-mozc のマニュアルテストを行って 6 件ぐらいバグを見つけたところ,作者様にものすごい勢いで修正していただきました.

あと,fcitx-mozc の再変換には,ibus-mozc の再変換用に私が書いたコードが使われていたりするようです.

今年の話とか

入社後なんだかんだでずっと Mozc に関わってきましたが,2014 年 1 月から社内の別プロジェクトに移ることにしました.オープンな場で何をやったか話すのはいつも通り書いたコードが公開されてからにしようかと思います.
Windows 版 Mozc は 20% の枠内で今後も協力していきたいところですが,ibus-mozc のメンテナンスまでやる余裕はなさそうです.ibus-mozc の今後の開発・メンテナンスを引き継いでもいいという方がいらっしゃいましたら社内外問わずお知らせください.

「ファイルの中身に依存しつつそのファイル自体を更新する」コマンドラインツールはビルドシステムと相性が悪い

関数型プログラミングのメリットについて語られることはあっても,「ファイルの中身に依存しつつそのファイル自体を更新する」コマンドラインツールがビルドシステムと相性が悪い,みたいな話をする人はあんまりいないようなので書いてみた.

Windows には signtool.exe というデジタル署名ツールがある.実行ファイル myapp.exeに署名したい場合は以下のようにする.

signtool sign /a myapp.exe

十分にシンプルなように見える.が,典型的なビルドシステムはこの処理がお気に召さない.試しに Chromium や node.js で使われているメタビルドシステムである Gyp で書いてみる.

{
  'targets': [
    {
      'target_name': 'myapp',
      'type': 'executable',
      'sources': [
        'main.cc',
      ],
    },
    {
      'target_name': 'sign',
      'type': 'none',
      'dependencies': [
        'myapp',
      ],
      'actions': [
        {
          'action_name': 'run_signtool',
          'inputs': [
            '<(PRODUCT_DIR)/myapp.exe',
          ],
          'outputs': [
            '<(PRODUCT_DIR)/myapp.exe',
          ],
          'action': ['signtool', 'sign', '/a', '<(PRODUCT_DIR)/myapp.exe'],
          'msvs_cygwin_shell': 0,
        },
      ],
    },
  ],
}

この Gyp ファイルから生成された Ninja 用ビルドファイルは,以下のようにビルド時にエラーとなる.

C:\work\dag>ninja -C out/Default sign
ninja: warning: multiple rules generate myapp.exe. builds involving this target will not be correct; continuing anyway
ninja: Entering directory `out/Default'
ninja: error: dependency cycle: myapp.exe -> myapp.exe

myapp.exe をビルドするのに myapp.exe が必要なのはけしからん,とのこと.
私の場合,ラッパースクリプトを書いて署名前後でファイルパスの一部が変わるようにして回避することが多い.
例1.

mysigntool --input=myapp.exe --output=myapp.signed.exe

例2.

mysigntool --input=unsigned/myapp.exe --output=signed/myapp.exe

デジタル署名に限らず,mt.exe を利用したマニフェストファイルの埋め込み,rebase.exe を利用した事前バインド,Profile Guided Optimization (PGO) などで,うっかり入力ファイルと出力ファイルを同一パスにすると同様の問題に直面する.

なんでこんな話を書く気になったかというと,暇つぶしに Gyp にパッチを書いていて突然気付いたからである.
https://codereview.chromium.org/82703007#msg3
Directed Acyclic Graph (DAG; 無閉路有向グラフ) を事前に構築できることを前提としているシステムというのは,ビルドシステムにしろ MapReduce にしろ,案外日常的にお世話になっている.そういうシステムで「ちょっとした処理」を行うとして,なんだかうまく書けないなぁという処理,実はそれ入力と出力に循環があったりしませんか,というまあただそれだけの話.
こうやって考えてみると,世間のビルドシステムというのはファイルシステムと割とべったりくっついているということに気付かされる.

まとめ

入力パス名と出力パス名が同じになる操作は,一般的にビルドシステムと相性が悪い.典型的なビルドシステムでは,ファイルの内容ではなくパス名だけを用いて依存関係を記述する必要があることに注意.

Windows の IME を変換エンジンとして使う

f:id:NyaRuRu:20131218051110p:plain
プロセス分離型の IME の開発に携わった以上,一度は試してみたいと思っていた奴,の基礎実証実験っぽいのをやってみた.Windows向け IME を (とくに個人規模で) 作っている人にはもしかしたら役に立つかも.

テーマ

メインテーマは,Windows で IME を実装するとして,バックグラウンドで別 IME を有効化し,その IME に対してクエリを投げ,返ってきた結果を利用するための技術的な枠組みについて.ここではプロセスモデル的に Windows で可能かどうかという点のみを考える.

大まかな流れ

今回試した手法では,Windows 8 で TSF に追加された ITfFnSearchCandidateProvider という仕組みを活用する.このインターフェイスは,複雑なことはできない反面,1) 文字列を投げて文字列のリストが返ってくるというシンプルな仕組みである,2) ステートを持たず,IME でユーザーが入力中かどうかに関わらず自由に使える,という点で大変使いやすい.ただしすべての IME が実装しているわけではない.これについては後述.
また,このインターフェイスには "Windows 8 [desktop apps only]" と注釈がついている.ここで,プロセス分離型の IME 実装を前提とするのが生きてくる.プロセス分離型の実装では,好きにデスクトッププロセスと連携できるのでこれは特に問題にはならない.

具体的な流れ

以下の手順で,キーボードフォーカスを持たないバックグラウンドプロセス内で,ITfFnSearchCandidateProvider をサポートした IME を変換エンジンとして使用できる.

  1. バックグラウンドスレッドを作る
  2. スレッド内で COM を初期化する (STA 推奨)
  3. ITfInputProcessorProfileMgr.ActivateProfile を使用して,使用したい IME を有効化する.
  4. ITfThreadMgr2.ActivateEx を呼んで IME に初期化処理を行わせる.
  5. ITfThreadMgr2.GetFunctionProvider に,対象 IME の CLSID を渡して ITfFunctionProvider を取得
  6. GetFunctionProvider.GetFunction 経由で ITfFnSearchCandidateProvider を取得
  7. ITfFnSearchCandidateProvider.GetSearchCandidates で任意のクエリを行う

罠ポイント

上記手順の 3 が問題. Windows 8 以降はデスクトッププロセス間で IME 関係の設定を共有するようになったため,うかつに別 IME を選択すると他スレッド/他プロセスにも影響が及んでしまう.これについては, API を使って一時的にこの挙動に関する設定そのものを変更することで回避できそう.具体的には,SystemParametersInfo API に SPI_GETTHREADLOCALINPUTSETTINGS/ SETTHREADLOCALINPUTSETTINGS を指定して,以前の「スレッドごとに IME 関係の設定を保持」に変更する.変更は永続的である必要はなくて,IME を変更するごく短い間だけで十分なようだ.というわけで先ほどの手順をもう少し改良する.

  1. バックグラウンドスレッドを作る
  2. スレッド内で COM を初期化する (STA 推奨)
  3. 「スレッドごとに IME 関係の設定を保持」が有効でなければ SETTHREADLOCALINPUTSETTINGS を使って有効にする
  4. ITfInputProcessorProfileMgr.ActivateProfile を使用して,使用したい IME を有効化する.
  5. ITfThreadMgr2.ActivateEx を呼んで IME に初期化処理を行わせる.
  6. ITfThreadMgr2.GetFunctionProvider に,対象 IME の CLSID を渡して ITfFunctionProvider を取得
  7. GetFunctionProvider.GetFunction 経由で ITfFnSearchCandidateProvider を取得
  8. 「スレッドごとに IME 関係の設定を保持」を変更していれば,SETTHREADLOCALINPUTSETTINGS で元に戻す
  9. ITfFnSearchCandidateProvider.GetSearchCandidates で任意のクエリを行う

実際には「スレッドごとの IME 設定」を元に戻すのはもう少し手前でも問題ないかもしれない.
また,ユーザーが「アプリウィンドウごとに異なる入力方式を設定する」を有効にしている場合,GETTHREADLOCALINPUTSETTINGS は TRUE を返す.この場合はスレッドごとに異なる IME を有効化できるため何もしなくてよい.

応用

上の罠ポイントさえ回避できれば,複数のバックグラウンドスレッドを用意し,それぞれに別 IME をロードして構わない.つまり,複数の IME に対して同時にクエリを行うことができる.

ITfFnSearchCandidateProvider をサポートしている IME はどれぐらいあるのか?

手元の Windows 8.1 環境でしらべたところ,MS-IME 日本語版と ATOK 2013 はサポートしている.Google 日本語入力 / OSS Mozc は1.12.1599.102 の時点で未サポート.以下,実験したい人のための GUID 一覧.

{
  name: "MS-IME Japanese (Windows 8.1)",
  langid: 0x0411,
  clsid: "03B5835F-F03C-411B-9CE2-AA23E1171E36",
  profile: "A76C93D9-5523-4E90-AAFA-4DB112F9AC76",
},
{
  name: "ATOK 2013",
  langid: 0x0411,
  clsid: "E7602D3E-204C-4662-B92F-78DF0DE5752D",
  profile: "3C4DB511-189A-4168-B6EA-BFD0B4C85615",
},
{
  name: "Google Japanese Input",
  langid: 0x0411,
  clsid: "D5A86FD5-5308-47EA-AD16-9C4EB160EC3C",
  profile "773EB24E-CA1D-4B1B-B420-FA985BB0B80D",
},
{
  name: "OSS Mozc",
  langid: 0x0411,
  clsid: "10A67BC8-22FA-4A59-90DC-2546652C56BF",
  profile: "186F700C-71CF-43FE-A00E-AACB1D9E6D3D",
},
{
  name: "Corvus-skk",
  langid: 0x0411,
  clsid: "EAEA0E29-AA1E-48ef-B2DF-46F4E24C6265",
  profile: "956F14B3-5310-4cef-9651-26710EB72F3A",
},

まとめ

Windows 8.1 環境で ITfFnSearchCandidateProvider を利用すると,MS-IME 日本語版と ATOK 2013 を(同時に)フォールバックエンジンとして使う IME が作成可能.

速度とか所感

MS-IME Japanese は大体 10 msec 以内で返ってくるのでサジェストでも大丈夫かも.ATOK 2013 はクエリが返ってくるまで 100 msec 以上かかっていて,リアルタイムのサジェストに使うのはちょっときついかも.

サンプルコード

次のコードは,現在使用中の IME からのみ ITfFnSearchCandidateProvider を取得することで色々処理を簡略化したもの.起動後に何か入力すると,その入力を ITfFnSearchCandidateProvider.GetSearchCandidates に渡した結果が返ってくる.空行入力で終了.
ビルドには要 NuGet.Package Manager から TSF.SearchCandidateProvider を追加.

PM > Install-Package TSF.SearchCandidateProvider
using System;
using System.Linq;
using TSF;

static class Program {
  static void Main(string[] args) {
    using (var provider = SearchCandidateProvider.FromCurrentProfile().Result) {
      if (provider == null) {
        Console.WriteLine("No provider found.");
        return;
      }
      Console.WriteLine("{0} is found", provider.Profile.Name);
      while (true) {
        var line = Console.ReadLine();
        if (string.IsNullOrEmpty(line)) {
          return;
        }
        var result = provider.GetSearchCandidates(line).Result;
        Console.WriteLine("Elapsed time: {0} msec", result.Elaplsed.TotalMilliseconds);
        result.Candidates.ToList().ForEach(s => Console.WriteLine("  " + s));
        Console.WriteLine();
      }
    }
  }
}

ビルドするのは面倒なのでビルド済みファイルが欲しいという人はこちら.
http://d.hatena.ne.jp/NyaRuRu/files/SearchCandidateProviderSample.zip

TSF.SearchCandidateProvider のコードが見たい人はこちらから.
NyaRuRu/TSF-SearchCandidateProvider · GitHub

類似研究

アレ用の何か
ITfFnReconversion を使う例.