Chrono Mock
言語日本語

Electron アプリで日付を偽装する

これを検索すると、どの答えも自分のコードを変更する話ばかりです。Date オブジェクトをモックする、テストフレームワークのフェイクタイマーを使う、など。ビルド済みのアプリしか手元にないときには、どれも役に立ちません。これは、すでにあるアプリをそのまま動かします。

なぜインジェクションが届かないのか

このモードがあるのは、ある具体的な失敗がきっかけです。Electron で作られたポモドーロタイマーを x60 のセッションで動かしたところ、カウントダウンがまったく進みませんでした。アプリのバンドルを分解すると、互いに独立した 2 つの理由が見つかり、どちらもネイティブ側には対処法がありませんでした。

  • カウントダウンは時計を読んでいませんでした。Web Worker の中で動く、setInterval で増えるカウンターだったのです。アプリが「いま何時か」を一度も尋ねないので、フックすべき時刻の呼び出し自体がありませんでした。
  • その Worker はサンドボックス化されたレンダラープロセスの中にあります。外部からインジェクトされたライブラリが入り込めない、まさにその場所です。

そのため、Windows 側の仕組みはここでは役に立ちません。答えは、代わりにアプリ自身の JavaScript エンジンと直接やり取りすることです。

代わりに何が起こるか

アプリはデバッグポートと、きれいな使い捨てのプロファイルで起動され、そのアプリ自身の時間関連 API が DevTools プロトコル経由でセッションの時計に載せ替えられます。対象は Date.now、new Date() コンストラクター、performance.now、setTimeout、setInterval です。

この上書きは、アプリが開くすべてのコンテキストに、Web Worker も含めて設定されます。タイマーがたいていいるのはそこであり、ネイティブのインジェクションがどうやっても届かない唯一の場所です。

操作方法は何も変わりません。同じコマンド、同じウィンドウ、2 つの時計と判定が並ぶ同じセッションパネルです。

# ネイティブアプリと同じコマンド - モードは自動で検出される
chrono run "%LOCALAPPDATA%\Programs\your-app\YourApp.exe" --at 2038-01-01T00:00:00 --mode x60

始める前に知っておくべき 2 つの制約

起動は侵襲的で、そのことを意図的に大きく伝えます。

アプリはリモートデバッグポートを開き、%TEMP% の下に作られた新しい隔離プロファイルで起動します。実際のプロファイルでは動かず、すでにシステムトレイに常駐しているコピーでもありません。プロファイルはセッション終了時に削除され、削除できなかった場合は、黙って残さずセッションの概要でそう報告します。

自分で指定した --user-data-dir や --remote-debugging-port は、上書きされずに拒否されます。セッションを実際のブラウザープロファイルに向けてしまうという分かりやすい事故は、許容する価値がないからです。

ページはセッションのタイムゾーンを読みます。

各ページとワーカーは、夏時間のない固定オフセットであるセッションのタイムゾーンに置かれ、自分が読むオフセットを確認します。そのため別のタイムゾーンを読んだページは報告されます。制約は 2 つあります。Intl はそのタイムゾーンを都市ではなく GMT+02:00 のようなオフセットで呼びます。また、Electron アプリケーションのメインプロセスはセッションが届かない Node で JavaScript を実行するため、実際の時計のままで、レポートにもそう記されます。

もう 1 つ、人を驚かせるのは、すでにスケジュールされたインターバルは、セッション中に速度を変えても元のペースを保つことです。Date.now、new Date()、performance.now はすぐに反応しますが、実行中の setInterval は反応しません。何も起きなかったと誤解させないよう、セッションはこの点について警告します。

どれほどテストされているか

実験的です。この言葉には、ここでは決まった意味があります。想定した対象では動作する、広範にはテストされていない、そして信頼できる情報源は、このページではなく内蔵の監査である、ということです。自動化されたテスト一式ではなく、64 ビット Windows 上の実際の Electron アプリで手作業により確認されました。

カバレッジはプロセス単位ではなく JavaScript コンテキスト単位で報告されます。ここで存在する単位がそれだからです。何もせず待機していて時刻を一度も尋ねないアプリは、正直に判定不能を返します。時計を読んでいないので、正しい時計を見たかどうかについては何も言えないのです。判定の意味 →

試してみる