2015 年やったこと - Mozc 編

1 年遅れぐらいになりますが,OSS Mozc 関係で 2015 年にやったことのまとめです.

空き時間を利用したプロダクトチーム外からのコミットということで,新規機能に関しては引き続き何も行っていません.前年と同じく OSS プロジェクトとしての環境整備と技術的負債の解消に注力した年でした.

以下が主な活動内容です.

  • Google Code (Subversion) から GitHub (git) への移行を完了
    • 過去のコミット履歴を可能な限り維持
    • ドキュメントを Markdown で書き直す
    • Travis CI および AppVeyor を利用し,サポートしている全プラットフォームについて CI (Continuous Integration) を実現
      • Android
      • Linux desktop
      • NaCl
      • Windows
      • macOS
  • コードの簡略化
    • プロダクションに使われなかった実験的コードの削除
    • サポートを終了した古い OS 向けコードの削除
    • 積極的にコンパイラをアップグレードし,C++ 11 のライブラリでコードを書き換え
      • Windows 環境は Visual C++ 2010 から 2013 を経て 2015 へ
      • 非 Windows 環境は GCC から Clang / libc++ に移行
      • base/hash_tables.hstd::unordered_map / std::unordered_set に置き換え
      • mozc::scoped_ptr<T>std::unique_ptr<T> に置き換え
  • Windows 10 向けの緊急性の高い問題の修正

コミット単位でみる作業まとめ

Windows XP 向けコードの削除

以前は最低サポート環境が Windows XP であったため,Windows Vista 以降に追加された API は GetProcAddress で動的にリンクするというスタイルで統一されていました.Windows XP 廃止後も同様のコードは動くことは動くのですが,コードが不必要に長くなるだけでなく,Loader Lock に代表されるような同期的モジュール解決の問題すらも不必要に呼び込んでしまいます.そこで,Windows Vista 以降に追加された API に関しても可能な限り暗黙的なリンク (Implicit Linking) に変更したのが主な変更内容です.

少し冒険してみた点としては,DLL の Import Library が提供されない input.dll についても,ビルドルールを工夫して暗黙的にリンクするようにしてみたということでしょうか.基本的には DLL から「正しい」LIB ファイルを作るには と同じことを行っています.

Android 版の不要なコードの削除

サポートバージョンを上げたことにより不要になったコードを削除しました.

Visual C++ 2013 への移行

Mozc の Windows 向けビルドは,2015 年始めの時点では依然として Visual C++ 2010 を使用しており,これが C++ 11 移行の大きな障害となっていました.これをまずは Visual C++ 2013 段階までアップグレードするという試みです.これに付随して,Windows Kit (Windows SDK) 8.1 に追加されたライブラリや定数を使ったコードの簡略化も行いました.

この過程で遂に実現できたもののひとつに,NOMINMAX マクロをプロジェクト全体に適用することに成功した,というものがあります.そうです,<windows.h> を include するだけで,min/max というプリプロセッサマクロが定義されてしまうという問題が,commit 2cc1a055 によってついに過去のものとなったのでした.

Visual C++ 2015 への移行準備

2015 年の年末にかけて,さらに Visual C++ 2013 から Visual C++ 2015 への移行の下準備を行いました.移行の完成は 2016 年を待つことになりますが,2015 年の段階である程度の下準備 (Google TestProtocol Buffers のアップデート等) を完了することができました.

C++ 11 対応

Visual C++ 2013 への移行でいよいよ C++ 11 対応の目途がたったことから,まずは全プラットフォームで C++ 11 が前提とできるようにビルドルールの変更等と行いました.この過程で STLPort を利用した Android ビルドを非サポートとしたり,非 Windows 環境のデフォルトコンパイラを Clang に統一したりといった変更も行っており,全体としてはビルドルールの簡略化に貢献できたものを考えています.

その後,実際に C++ 11 の新しいライブラリを用いて以下のような置き換えを行いました.技術的に難しいところはないものの,単純に多数のファイルを変更する必要があるため,時間に余裕があるときでないとなかなか難しい作業です.

  • base/hash_tables.hstd::unordered_map / std::unordered_set に置き換え
  • mozc::scoped_ptr<T>std::unique_ptr<T> に置き換え

実験的コードの削除

Remove unused code from mozc_tool · google/mozc@51b1f78 · GitHub

もしかしたら将来使うかも,と導入したものの,結局プロダクションで使われなかったコードの削除も行いました.例えば上記コミットで削除されているコードは,1,000 行以上もありながら Windows 専用かつプロダクションでは一度も使われていないという正真正銘デッドコードです.

ibus-mozc 廃止に向けた下準備

予定されていた ibus-mozc廃止は結局 2015 年中に行われませんでしたが,そのための下準備としていくつか ibus-mozc 関連のコードのクリーンアップを行っています.

一番入れたかった変更は commit 7a129e65 で,mozc_server 中にある ibus-mozc 専用コードを今のうちに削除しておくというものです.このコードは,IMR_QUERYCHARPOSITION NSTextInputClient firstRect と同様の機能をなんとか Gtk+ IM Module 上に実現しようとしたものの名残で,クライアントコードはなるべく簡潔にするという Mozc の設計思想に基づき mozc_server 側にエミュレーションコードが実装されました.しかしながら,Mozc Issue #243 で議論されているように本質的に解決できないコーナーケースが存在すること,および ibus-mozc 以外では使用されていないこと,という 2 点の理由から,ibus-mozc 廃止前に完全に削除しておきたかったのでした.

GitHub への移行と Continuous Integration (CI) の整備

Google Code のサービスを終了にともない,OSS Mozc についても GitHub への移行を行うこととなりました.当時は私自身あまり git に慣れていなかったこともあり,Subversion から git への移行をあれこれ試行錯誤するのにずいぶんと時間を使ったように思います.

とはいえ移行が済んだあとは GitHub のエコシステムに感心するばかりでした.特に CI については最終的にサポートしている全プラットフォームについて自動ビルドをセットアップすることができ,マルチプラットフォーム対応に価値を置いているプロジェクトとしては本当に助かっています.

Travis CI のセットアップ

AppVeyor のセットアップ

gclient から git submodule への移行

Windows 10 対応

Windows 10 で状況が変わった点についてもいくつか緊急的な修正を行いました.

  • 言語バーが Store App でも使われるようになったことへの対処
  • サンドボックス下で実行されるアプリケーションから mozc_server プロセスを起動できないという制約が Windows 8.1 時代よりも目立つようになったため,いくつかの対症療法的な変更

不安定なテストの改善

Travis CI 上で不安定だったテストについて安定化を試みました.

透明度付き PNG ファイルを PBGRA32 形式の Bitmap ファイルの変換するツールを作成

ちょうど 2015 年,会社のロゴが更新され,候補ウィンドウに表示される画像も変更する必要ができました.

Windows 版の mozc_renderer は,会社ロゴの表示に GDI の AlphaBlend API を利用しています.問題はこの API はいわゆる乗算済みアルファ フォーマット (正確には PBGRA32 形式) を期待していることです.デザイナーさんから渡された PNG ファイルを PBGRA32 に変更する必要があったですが,適当なツールがみつからなかったので急遽作ったのがこちらのコードでした.

雑感

以前から興味のあった GitHub 上での作業や CI について一通り体験することができたのは非常に有意義でした.また,git filter-branchgit rebase を使ったコミット履歴の編集を学ぶ上でも良い機会でした.

一方,前年に引き続きメンテナンス作業に注力して実感したのは,ソフトウェアのメンテナンスというのは本当にコストがかかるというというものでした.OSS Mozc に関して言うと,以下の作業だけで年間 100 時間は軽くかかっています.

  • コンパイラのバージョンを上げる
  • 依存するライブラリのバージョンを上げる
  • 丁寧にリリースノートを書く
  • ドキュメントを最新の状態に維持する
  • Issure Tracker に報告された問題に丁寧に答える

自分の場合これらの作業に有給休暇 10 日分以上を消費していることになるわけで,今後も同程度の貢献が継続可能かというと大変に疑問です.各種 Linux ディストリビューションで OSS Mozc が採用されるのはありがたい一方,最大の不安は期待値が高くなりすぎることです.将来のメンテナンスも含めて,あくまで無保証で提供されているという点を今一度思い出していただければという次第.

過去の関連エントリ

Android 7.1 に追加した API

github.com github.com github.com github.com

  • BaseInputConnection#commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle)
  • EditorInfo#contentMimeTypes
  • InputConnection#INPUT_CONTENT_GRANT_READ_URI_PERMISSION
  • InputConnection#commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle)
  • InputConnectionWrapper#commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle)
  • InputContentInfo#InputContentInfo(android.net.Uri, android.content.ClipDescription)
  • InputContentInfo#InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri)
  • InputContentInfo#describeContents()
  • InputContentInfo#getContentUri()
  • InputContentInfo#getDescription()
  • InputContentInfo#getLinkUri()
  • InputContentInfo#writeToParcel(android.os.Parcel, int)
  • InputContentInfo#CREATOR

Android 7.0 Nougat に追加した API

github.com

  • View#dispatchFinishTemporaryDetach()
  • View#dispatchStartTemporaryDetach()
  • View#isTemporarilyDetached()

github.com

  • InputConnection#closeConnection()
  • InputConnectionWrapper#closeConnection()
  • BaseInputConnection#closeConnection()

github.com

  • InputConnection#getHandler()
  • InputConnectionWrapper#getHandler()
  • BaseInputConnection#getHandler()

github.com

  • Settings#ACTION_KEYBOARD_LAYOUT_SETTINGS

github.com

  • TextView#getImeHintLocales()
  • TextView#setImeHintLocales()

github.com

  • LocaleSpan#LocaleSpan(LocaleList)
  • LocaleSpan#getLocales()

github.com

  • SuggestionSpan#getLocaleObject()

github.com

  • InputConnection#deleteSurroundingTextInCodePoints(int, int)

github.com

  • InputMethodManager#dispatchKeyEventFromInputMethod(View, KeyEvent)

github.com

  • InputMethodSubtype#getLanguageTag()
  • InputMethodSubtype.InputMethodSubtypeBuilder#setLanguageTag(String)
  • SpellCheckerSubtype#getLanguageTag()

github.com

  • EditorInfo#locales()

github.com

  • LocaleList#describeContents()
  • LocaleList#writeToParcel(Parcel, int)
  • LocaleList#CREATOR

Windows 10 Insider Preview Build 14332 実験ノート: ブラウザと IME のプライベートモード連携

Windows 10 Preview ビルド 14328 でお試しいただける日本語 IME の変更点について – Windows & Devices 開発統括部

入力履歴の管理が便利になりました。人に見られたくない履歴が候補リストに表示されて困った経験はありませんか?
プライベートモード(仮称)を使えば、その間に蓄積された入力履歴はモードの終了時には削除され、その後意図しない場面で表示されてしまうことを防ぐことができます。
プライベートモード(仮称)はIME のアイコンをクリックして表示されるメニューから「プライベートモード」を選択することでオン・オフすることができます。Edge や Internet Explorer で InPrivate ブラウズ機能をお使いの場合は、本機能が自動的に オン・オフ されます。
プライベートモード(仮称)で蓄積された入力履歴はプライベートモードの間だけ有効となり、モードの終了時には削除されます。それまでにプライベートモード以外で蓄積されていた入力履歴のデータには影響ありません。

ブラウザと IME のプライベートモード連携は,自分にとっても時間があったらやれたらいいなぁと思っていた機能だったこともあって,週末を利用して少し調べてみました,がどうも時間切れっぽいのでメモだけでも残しておきます.想定読者はブラウザ開発者,IME 開発者,および IME API 開発者という感じですが,正直に言えば自分用のメモです.

この機能,名前に (仮称) が付いているようにまだまだフィードバックしだいで色々変わりうる段階と思われます.というわけでまだちょっと調べたりないのですが,実験したい人向けにメモでも残しておく次第です.

疑問点

  1. そもそも Internet Explorer / Edge と MS-IME はどのようなプロトコルでこの動作を行っているのか?
  2. サードパーティ製のブラウザと MS-IME の組み合わせで同じことができるか?
  3. Internet Explorer / Edge とサードパーティー製の IME で同じことができるか?

今回の調査結果

そもそも Internet Explorer / Edge と MS-IME はどのようなプロトコルでこの動作を行っているのか?

時間内に絞り込み切れませんでした.無念……

サードパーティ製のブラウザと MS-IME の組み合わせで同じことができるか?

上の結果が確定しなかったため確たる結論に至れず.残念……

Internet Explorer / Edge とサードパーティー製の IME で同じことができるか?

公開 API のみで,InPrivate モードの検出が可能であることまでは確認.少なくとも見かけ上は可能.

調べたこと

  • ITfTextInputProcessorEx::ActivateEx に指定される flags は,InPrivate かどうかで変化するか?
  • ITfThreadMgr::GetGlobalCompartmentITfCompartmentMgr::EnumCompartments の結果は InPrivate かどうかで変化するか?
  • ITfThreadMgr::QueryInterfaceITfCompartmentMgr::EnumCompartments の結果は InPrivate かどうかで変化するか?
  • ITfDocumentMgr::QueryInterfaceITfCompartmentMgr::EnumCompartments の結果は InPrivate かどうかで変化するか?
  • ITfContext::QueryInterfaceITfCompartmentMgr::EnumCompartments の結果は InPrivate かどうかで変化するか?
  • ITfContext::EnumProperties で列挙される ITfProperty に対応しそうなものはあるか?
  • Edge の設定する InputScope は InPrivate かどうかで変化するか?
  • InputScope の一種 IS_PRIVATE (== 61) で MS-IME は動作を変化させるか?
  • Windows 10 Anniversary SDK Preview Build 14332 の API ヘッダの差分および InPrivate と付いた API に関連しそうなものはあるか?

得られた知見

  • Build 14332 時点で,正式な InPrivate 連携 API が Text Services Framework (TSF) に存在するかどうかは疑わしい.InputScope を含め,TSF の API で観測可能な範囲内に InPrivate モードの有効無効を示唆するものは一切見つけられなかった.
  • Build 14332 時点で,InputScope の一種 IS_PRIVATE によって MS-IME をプライベートモードにすることはできなかった.
  • Internet Explorer であれば,ieframe.dll のエクスポート関数 IEIsInPrivateBrowsing が依然として利用可能.ただしこの API は Edge のプロセスには読み込まれない.
  • Windows 10 SDK では,ProcessInPrivateInfo という列挙値が processthreadsapi.h に追加されている (少なくとも 10.0.10586.0 SDK の時点で既に存在する).
typedef enum _PROCESS_INFORMATION_CLASS {
    ProcessMemoryPriority,
    ProcessMemoryExhaustionInfo,
    ProcessAppMemoryInfo,
    ProcessInPrivateInfo,
    ProcessInformationClassMax
} PROCESS_INFORMATION_CLASS;

Build 14332 時点で,以下のコードは Internet Explorer および Edge 共に InPrivate モードのみ true を返す.

bool is_process_in_private() {
  BYTE value = 0;
  if (!::GetProcessInformation(GetCurrentProcess(),
                               ProcessInPrivateInfo,
                               &value,
                               sizeof(value)) {
    return false;
  }
  return value != 0;
}

フィードバックアイディア

ちょっと裏付け不足でまだ未登録なのですが,概ね以下の点についてもうちょい調べてみないとなーと思っております.

  • プライベートモード連携に関しては,TSF の公開 API 内で完結させ,サードパーティー製ブラウザおよびサードパーティー製 IME との相互運用性に配慮して欲しい.ファーストパーティ製ブラウザおよびファーストパーティ製 IME の間のみで使われるプライベートプロトコルは好ましくない.
  • ProcessInPrivateInfo は,現状取得のみが可能なように見える.これに該当する特殊プロセスの作成に非公開 API が必要なのであれば,ファーストパーティ製ブラウザのみがそれを使用可能という状況は好ましくない.

関連事例

(同一プロセスで動く IME で使うことを前提とした) Accessibility API を利用したプライベートモード検出

書くいう自分も,2013 年末ごろには MSAA を使ってプライベートモードの検出ができるかどうか実験したりしていました,がチームを移ったあとのごたごたで結局プロダクションでは使われてなかったりします.その辺のコードがいまもここに.

mozc/src/win32/base/browser_info.cc BrowserInfo::IsInIncognitoMode

sufix は typo ですすみません.あとで直します……

Chromium Bug 311180: Chrome OS: Incognito window is not really incognito for IME users

Issue 311180 - chromium - Chrome OS: Incognito window is not really incognito for IME users - Monorail

Chromium OS での同様の機能リクエスト.長いこと放置されていて辛い感じです.

Chromium Bug 601951: Android keyboard suggestions leak information typed in incognito window

Issue 601951 - chromium - Android keyboard suggestions leak information typed in incognito window - Monorail

Android 版 Chromium での同様の機能リクエスト.

この件については SwiftKey のサポートページにも以下のように書かれていたり.

Can I turn off learning in incognito mode with SwiftKey Keyboard for Android? – SwiftKey Support

Unfortunately, the keyboard actually does not know that you are in this special mode in the Chrome app because that is not something Google passes on to other apps.

改定履歴

  • 2016-05-02: ProcessInPrivateInfo の追加時期について表現を変更.少なくとも 10.0.10586.0 SDK の時点で存在する.