在 Linux 中窃取 Firefox 的焦点

如何在旧版 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

扩展

只要只有一个浏览器窗口,这个简单的实现就可以很好地工作。当涉及多个窗口时,会出现几个挑战

  • 即使可能会打开新窗口,原生事件也必须继续流向活动窗口。然而,大多数窗口管理器会将焦点给予新打开的窗口。
  • 窗口切换:当希望切换到另一个窗口时,必须移动焦点。这需要 WebDriver 的 Firefox 扩展和此组件之间的合作。
  • 关闭窗口:当关闭窗口时,焦点必须移动到另一个窗口。按照设计,如果关闭了活动窗口,WebDriver 不会保证任何事情 - 直到切换到新窗口为止。在这种情况下,必须特别小心。

与其他组件的交互

基本思路不需要与 WebDriver 的其他组件进行交互。但是,当涉及多个窗口 - 创建、切换或销毁时,此组件应该意识到这一点。无法跟踪新窗口的创建 - 因为它可能是许多操作的副作用。可以跟踪切换和关闭。

涉及的技术

要理解这个解决方案,应该熟悉 X-Windows 及其事件。了解 GDK 事件处理循环也很有用。

实现细节

所有这些都描述了 firefox/src/cpp/linux-specific/x_ignore_nofocus.c 中的代码。

共享库

劫持事件是通过覆盖 XNextEvent 来完成的。使用 LD_PRELOAD 加载包含 XNextEvent 修改实现的共享库。修改后的函数打开 /usr/lib/libX11.so.6 并调用真正的函数。然后检查真正的函数返回的事件(即真正的事件)。

识别事件

在基本思路下,FocusOut 事件将被简单地丢弃。但是,窗口切换使事情复杂化。

数据结构

有一个全局数据结构,它记住以下信息

  • 活动窗口 ID(如果此时有这样的 ID)
  • 正在创建的新窗口的 ID(同样,如果存在)
  • 窗口切换是否正在进行中。
  • 窗口关闭是否正在进行中。
  • 焦点是否已给予另一个窗口,是否应将其窃取回活动窗口?
  • 活动窗口是否已收到 FocusIn 事件?
  • 我们是否将当前活动窗口设置为关闭操作的结果?

Firefox 启动

FocusIn 事件到达,活动窗口 ID 为 0。设置新的活动窗口。请注意,在创建主窗口期间,会创建另一个子窗口,并且会向活动窗口发送 FocusOut 事件。幸运的是,此 FocusOut 事件表明焦点将要移动到子窗口(由 NotifyInferior 标识),因此它是允许的。

用户已切换到另一个窗口

这由一个 FocusOut 事件表示,该事件的 detail 字段既不是 NotifyAncestor 也不是 NotifyInferior。此事件被简单地丢弃,并替换为 KeymapNotify 事件,该事件会被 GDK 立即丢弃。

正在创建新窗口

此条件由 ReparentNotify 事件标识。发生这种情况时,new_window 字段将被设置为新创建的窗口的 ID。将允许后续的 FocusOut 事件 - 在新窗口创建期间,事件将像往常一样流动(来自活动窗口的 FocusOut 事件,到新窗口的 FocusIn 事件,到新窗口的 FocusOut 事件以及到新窗口的子窗口的 FocusIn 事件)。在新窗口的子窗口收到 FocusIn 后,将发出对 XSetInputFocus 的调用,以将焦点返回到活动窗口。

发生窗口切换

在窗口切换期间,事件将正常流动。当窗口的子窗口收到 FocusIn 事件时,窗口切换被认为完成。窗口切换从标识文件 /tmp/switch_window_started 开始。在此文件中,将写入 switch: 字符串,后跟窗口 ID(该 ID 仅用于调试目的)。这会将活动窗口 ID 更改为 0,并将状态更改为“正在切换”。在切换期间(或当没有活动窗口时)不会丢弃任何事件。

正在关闭窗口

与窗口切换非常相似(也通过读取文件来识别)。但是,它表明正在关闭窗口 - 如果已关闭,则不会发生焦点窃取。此外,还标识了 DestroyNotify 事件,以查找活动窗口何时被关闭(由用户显式关闭,或由其他操作隐式关闭,而不是显式调用关闭)。在这种情况下,活动窗口 ID 也将设置为 0。

上次修改时间为 2022 年 1 月 10 日:更多 wiki (#907) [部署站点] (adcf706a1ad)