Linux での Firefox からのフォーカス奪取
このドキュメントは以前、wiki に掲載されていました
このページでは、Linux 上でのネイティブイベント実装の重要なコンポーネントであるフォーカス維持について説明します。ネイティブイベントが Firefox で処理されるためには、常にフォーカスを維持する必要があります。ユーザーが別のウィンドウに切り替えることを決定した場合(理解できることですが)、Firefox はフォーカスを失ったことを認識してはなりません。
ソリューションの概要
基本的な考え方
基本的な考え方は、XLib(X-Windows クライアントライブラリ)レイヤーとアプリケーションの間に入り込むことです。X-Windows は、非同期イベントによってイベント(ユーザー入力、ウィンドウの破棄、マウスの動き)をアプリケーションに通知します。フォーカスを失ったことを示すイベント FocusOut は破棄されます。この考え方は、Jordan Sissel 氏による XNextEvent をオーバーライドするプリロードライブラリの実装に基づいています。 http://www.semicomplete.com/blog/geekery/xsendevent-xdotool-and-ld_preload.html を参照してください。
拡張機能
このシンプルな実装は、ブラウザウィンドウが 1 つしかない限りうまく機能します。複数のウィンドウが関与する場合、いくつかの課題が発生します。
- 新しいウィンドウが開かれても、ネイティブイベントはアクティブなウィンドウに流れ続ける必要があります。しかし、ほとんどのウィンドウマネージャは、新しく開かれたウィンドウにフォーカスを与えます。
- ウィンドウの切り替え:別のウィンドウに切り替えたい場合、フォーカスを移動する必要があります。これには、WebDriver の Firefox 拡張機能とこのコンポーネント間の連携が必要です。
- ウィンドウを閉じる:ウィンドウが閉じられると、フォーカスは別のウィンドウに移動する必要があります。WebDriver は、アクティブなウィンドウが閉じられた場合、新しいウィンドウに切り替えられるまで何も保証しません。このような状況では、特別な注意が必要です。
他のコンポーネントとの相互作用
基本的な考え方では、WebDriver の他のコンポーネントとの相互作用は必要ありません。ただし、複数のウィンドウが関与する場合(作成、切り替え、または破棄)、このコンポーネントはそれを認識している必要があります。新しいウィンドウの作成は、多くの操作の副作用として発生する可能性があるため、追跡できません。切り替えとクローズは追跡できます。
関連技術
このソリューションを理解するには、X-Windows とそのイベントに精通している必要があります。GDK イベント処理ループの知識も役立ちます。
実装の詳細
これらはすべて、firefox/src/cpp/linux-specific/x_ignore_nofocus.c
のコードについて説明しています。
共有ライブラリ
イベントのハイジャックは、XNextEvent をオーバーライドすることによって行われます。XNextEvent
の修正された実装を含む共有ライブラリは、LD_PRELOAD
を使用してロードされます。修正された関数は /usr/lib/libX11.so.6
を開き、実際関数を呼び出します。次に、実際関数が返すイベント(つまり、実際のイベント)が検査されます。
イベントの識別
基本的な考え方では、FocusOut
イベントは単に破棄されます。ただし、ウィンドウの切り替えによって事態が複雑になります。
データ構造
次の情報を記憶するグローバルデータ構造があります。
- アクティブなウィンドウ ID(現時点である場合)
- 作成中の新しいウィンドウの ID(再度、存在する場合)
- ウィンドウの切り替えが進行中の場合。
- ウィンドウのクローズが進行中の場合。
- フォーカスが別のウィンドウに与えられ、アクティブなウィンドウに奪い返す必要があるか?
FocusIn
イベントがアクティブなウィンドウによってすでに受信されたか?- クローズ操作の結果として現在アクティブなウィンドウを設定したか?
Firefox が起動します
FocusIn
イベントが到着し、アクティブなウィンドウ ID が 0 です。新しいアクティブなウィンドウが設定されます。メインウィンドウの作成中、別のサブウィンドウが作成され、FocusOut
イベントがアクティブなウィンドウに送信されることに注意してください。幸いなことに、この FocusOut
イベントは、フォーカスがサブウィンドウ(NotifyInferior
によって識別される)に移動することを示しているため、許可されます。
ユーザーが別のウィンドウに切り替えました
これは、詳細フィールドが NotifyAncestor
でも NotifyInferior
でもない FocusOut
イベントによって示されます。このイベントは単に破棄され、GDK によって速やかに破棄される KeymapNotify
イベントに置き換えられます。
新しいウィンドウが作成されています
この状態は、ReparentNotify
イベントによって識別されます。これが起こると、new_window フィールドは新しく作成されたウィンドウの ID に設定されます。後続の FocusOut
イベントは許可されます。新しいウィンドウの作成中に、イベントは通常どおりに流れます(アクティブなウィンドウからの FocusOut
イベント、新しいウィンドウへの FocusIn
イベント、新しいウィンドウへの FocusOut
、および新しいウィンドウのサブウィンドウへの FocusIn
)。新しいウィンドウのサブウィンドウが FocusIn
を受信した後、XSetInputFocus
の呼び出しが発行され、フォーカスがアクティブなウィンドウに戻されます。
ウィンドウの切り替えが発生します
ウィンドウの切り替え中、イベントは正常に流れます。ウィンドウの切り替えは、ウィンドウのサブウィンドウが FocusIn
イベントを受信したときに完了したと見なされます。ウィンドウの切り替えは、ファイル /tmp/switch_window_started
を識別することによって開始されます。このファイルには、ウィンドウ ID に続く switch:
文字列が書き込まれます(ID はデバッグ目的のみ)。これにより、アクティブなウィンドウ ID が 0 に変更され、状態が「切り替え中」に変更されます。切り替え中(またはアクティブなウィンドウがない場合)は、イベントは破棄されません。
ウィンドウが閉じられています
ウィンドウの切り替えと非常によく似ています(ファイルを読むことによっても識別されます)。ただし、ウィンドウが閉じられていることが示されています。閉じられた場合、フォーカス奪取は行われません。さらに、DestroyNotify
イベントは、アクティブなウィンドウが閉じられている場合(ユーザーによって明示的に、またはクローズへの明示的な呼び出しではない他の操作によって暗黙的に)を検出するために識別されます。この場合、アクティブなウィンドウ ID も 0 に設定されます。
重要なリンク
- Jordan Sissel 氏のオリジナルの XSendEvent ハック
- XLib イベント および XLib プログラミングマニュアル
- X プログラミングマニュアル/仕様