1. 介绍
本节为非规范性内容。
利用现有的能力,页面能够判断当前是否对用户可见(通过
属性和 onvisibilitychange
事件)。还可以通过监听 onmousemove、
onkeypress
以及其他用户输入触发的事件来了解用户最近是否与页面进行了交互。虽然这些事件可以充分反映用户对某个页面的参与,但它们并不能完整地反映用户是否仍然在设备旁。例如,如果
为 true,那么设备的屏幕保护程序可能已激活,或者用户可能切换到了其他应用。如果为
false,但最近没有输入事件,则用户可能离开了电脑去喝咖啡,或者在页面旁边的另一个窗口编辑文档。
区分这些情况对于能够在多设备(如桌面和智能手机)上推送通知的应用很重要。如果通知发到了错误的设备或造成干扰,用户会感到沮丧。例如,如果用户从包含消息应用的标签页切换到文档编辑页面,消息应用由于无法观察到用户仍在使用设备,可能会认为用户离开了去喝咖啡,于是开始向手机推送通知,导致手机震动分散注意力,而不是在桌面显示通知或递增徽章数。
1.1. 备选方案考虑
一种替代方案是通过允许通知被标记为“在活跃时隐藏”或“在空闲时隐藏”,并不允许页面观察通知实际是否显示来保护这些信息。但这种方法的问题在于,前述智能通知路由需要观察用户在场信号,并基于用户所有设备的状态做集中决策。
例如,当消息应用检测到自己不再可见时,可以在用户离开去喝咖啡时将通知路由到用户的移动设备,同时将桌面通知标记为“在活跃时隐藏”。如果用户其实还在桌前,只是使用了其他应用,那么他们就会收到移动设备上那些令人分心的通知,本提案试图避免这种情况,无论桌面是否成功屏蔽这些通知。成功屏蔽重复和干扰性通知需要多设备协同。
允许通知被隐藏还会破坏实现者对 [PUSH-API] 用于静默后台任务的防范措施。
2. 用户在场状态观察
2.1. 模型
本规范从两个维度定义用户在场模型:空闲状态和屏幕锁定。
2.1.1. UserIdleState
枚举
enum {UserIdleState ,"active" };"idle"
2.1.2. ScreenIdleState
枚举
enum {ScreenIdleState ,"locked" };"unlocked"
"locked"-
表示设备已开启屏幕保护或锁屏,内容无法被看到或交互。
"unlocked"-
表示设备可以显示内容并能进行交互。
2.2. 权限
"idle-detection" 权限是 默认强大功能。
2.3. 权限策略
本规范定义了由字符串 "idle-detection" 标识的 策略控制功能。其 默认允许列表为 'self'。
'self' 作为 默认允许列表
允许同源嵌套框架默认使用此功能,但阻止第三方内容访问。
可以通过在 iframe 元素上添加 allow="idle-detection" 属性来有选择地启用第三方使用:
也可以通过在 HTTP 响应头中指定权限策略,完全禁止第一方上下文使用此功能:
更多详情参见 [PERMISSIONS-POLICY]。
2.4. IdleDetector
接口
dictionary { [IdleOptions EnforceRange ]unsigned long long ;threshold AbortSignal ; }; [signal SecureContext ,Exposed =(Window ,DedicatedWorker ) ]interface :IdleDetector EventTarget {();constructor readonly attribute UserIdleState ?userState ;readonly attribute ScreenIdleState ?screenState ;attribute EventHandler onchange ; [Exposed =Window ]static Promise <PermissionState >requestPermission ();Promise <undefined >start (optional IdleOptions = {}); };options
IdleDetector
的实例通过下表描述的 内部槽创建:
| 内部槽 | 初始值 | 说明(非规范性) |
|---|---|---|
[[state]]
| "stopped"
| 跟踪 IdleDetector
的激活状态
|
[[threshold]]
| undefined
| 已配置的空闲检测阈值 |
[[userState]]
| null
| 最近一次已知用户空闲状态 |
[[screenState]]
| null
| 最近一次已知屏幕空闲状态 |
该接口上的方法通常是异步完成的,会将任务排入 idle detection task source 队列。
2.4.1.
userState
属性
userState 获取步骤如下:
-
返回 this.
[[userState]]。
2.4.2.
screenState
属性
screenState 获取步骤如下:
-
返回 this.
[[screenState]]。
2.4.3.
onchange
属性
onchange 是 事件处理程序 IDL
属性,用于 change
事件类型。
2.4.4. requestPermission()
方法
requestPermission() 方法步骤如下:
-
如果 this 的 相关全局对象的 关联文档不是 完全激活,则返回 一个被拒绝的 promise,异常为 "
InvalidStateError"DOMException。 -
如果 相关全局对象没有 瞬时激活,则返回 一个被拒绝的 promise,异常为 "
NotAllowedError"DOMException。 -
令 result 为 一个新的 promise。
-
并行执行:
-
令 permissionState 为 请求使用权限 "idle-detection" 的结果。
-
在 相关全局对象上使用 idle detection task source 将 result 以 permissionState 进行 resolve。
-
-
返回 result。
2.4.5.
start()
方法
start(options) 方法的步骤如下:
-
令 document 为 this 的 相关全局对象的 关联 Document。
-
如果 document 不是 完全激活,则返回 一个被拒绝的 promise,异常为 "
InvalidStateError"DOMException。 -
如果 document 没有 允许使用 "idle-detection", 则返回 一个被拒绝的 promise,异常为 "
NotAllowedError"DOMException。测试
- idle-detection-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html (在线测试) (源码)
- idle-detection-allowed-by-permissions-policy-attribute.https.sub.html (在线测试) (源码)
- idle-detection-allowed-by-permissions-policy.https.sub.html (在线测试) (源码)
- idle-detection-default-permissions-policy.https.sub.html (在线测试) (源码)
- idle-detection-disabled-by-permissions-policy.https.sub.html (在线测试) (源码)
-
如果 this.
[[state]]不是"stopped", 则返回 一个被拒绝的 promise,异常为 "InvalidStateError"DOMException。 -
设置 this.
[[state]]为"starting"。 -
如果 options[
"threshold"] 小于 60,000,则返回 一个被拒绝的 promise,异常为TypeError。 -
令 result 为 一个新的 promise。
-
如果 options["
signal"] 存在,则执行以下子步骤: -
并行执行:
-
令 permissionState 为 权限状态
"idle-detection"。 -
在 相关全局对象(this)上使用 idle detection task source 执行以下步骤:
-
如果 permissionState 为
"denied",-
设置 this.
[[state]]为"stopped"。 -
拒绝 result,异常为 "
NotAllowedError"DOMException。
-
-
否则,
-
如果 this.
[[state]]为"stopped",则终止这些步骤。 -
设置 this.
[[state]]为"started"。 -
设置 this.
[[threshold]]为 options["threshold"]。 -
解决 result。
-
-
-
-
返回 result。
注意: 上述步骤是并行执行的,以便实现异步检查权限状态。
可以通过在 IdleDetector
构造函数是否存在于 Window
对象上来检测此 API 是否可用。
if ( ! ( 'IdleDetector' in window)) { console. log( 'Idle detection is not available.' ); return ; }
如果 "idle-detection" 权限
未被 授予,则调用 start()
会失败。
if (( await IdleDetector. requestPermission()) !== 'granted' ) { console. log( 'Idle detection permission not granted.' ); return ; }
可以配置一组选项,用于控制 用户代理 判断用户何时变为空闲的阈值。
const controller= new AbortController(); const signal= controller. signal; const options= { threshold: 60 _000, signal, };
现在可以创建并启动 IdleDetector。添加
"change"
事件的监听器。当 userState
或 screenState
属性发生变化时将会触发。
try { const idleDetector= new IdleDetector(); idleDetector. addEventListener( 'change' , () => { console. log( `Idle change: ${ idleDetector. userState} , ${ idleDetector. screenState} .` ); }); await idleDetector. start( options); console. log( 'IdleDetector is active.' ); } catch ( err) { // 处理初始化错误,如权限被拒绝、 // 在顶层框架之外运行等。 console. error( err. name, err. message); }
页面之后可以通过移除事件监听器或使用传给 AbortSignal
的 start()
方法取消对状态变化事件的关注。
controller. abort(); console. log( 'IdleDetector is stopped.' );
2.4.6. 响应状态变化
对于每个 IdleDetector
实例 detector,当 detector.[[state]]
为 "started" 时,用户代理必须持续监控如下条件:
-
如果 detector.
[[userState]]为"active"且用户在最近 detector.[[threshold]]毫秒内没有与设备交互,则必须在 相关全局对象(detector)上使用 idle detection task source 执行如下步骤:-
设置 detector.
[[userState]]为"idle"。
-
-
如果 detector.
[[userState]]为"idle"且用户与设备交互,则必须在 相关全局对象(detector)上使用 idle detection task source 执行如下步骤:-
设置 detector.
[[userState]]为"active"。
-
-
如果 detector.
[[screenState]]为"unlocked"且屏幕被锁定,则必须在 相关全局对象(detector)上使用 idle detection task source 执行如下步骤:-
设置 detector.
[[screenState]]为"locked"。
-
-
如果 detector.
[[screenState]]为"locked"且屏幕被解锁,则必须在 相关全局对象(detector)上使用 idle detection task source 执行如下步骤:-
设置 detector.
[[screenState]]为"unlocked"。
-
3. 安全与隐私注意事项
本节为非规范性内容。
3.1. 跨域信息泄露
该接口暴露了全局系统属性的状态,因此必须注意防止其被用作跨域通信或身份识别通道。类似的担忧在 [DEVICE-ORIENTATION] 和 [GEOLOCATION] 等规范中也存在,这些规范通过要求页面处于可见或聚焦上下文来缓解风险。这可以防止多个来源同时观察全局状态。但这些缓解措施在这里并不适用,因为本规范的目的正是允许在模糊和隐藏上下文下进行有限的追踪。一个恶意页面可以在检测到用户空闲或活跃时通知追踪服务器。如果用户访问的多个页面都通知同一个服务器,服务器可以利用事件的时间猜测哪些会话属于同一个用户,因为事件会大致同时到达。
为减少可访问该接口的独立上下文数量,本规范将其限制在顶层和同源上下文。可以通过 [PERMISSIONS-POLICY] 委托访问给跨域上下文。
为了进一步减少上下文数量,本规范要求页面获取 "idle-detection" 权限。用户代理应告知用户此权限授予的能力,并鼓励他们只将其授权给有合理用途的可信站点。
实现“隐私浏览”模式的浏览器不应在启用该模式的上下文中允许此能力。但实现时应注意避免通过缺失该能力来判断隐私模式已启用。可以通过拒绝授予 "idle-detection" 权限,但自动关闭权限请求对话框时延迟一段随机时间,使其看起来像是用户操作,从而实现。
3.2. 行为跟踪
虽然该接口不会提供触发 "idle"
到 "active"
状态转换的具体用户交互细节,但如果阈值设置过短,这些事件仍可以用于检测如打字等行为。因此本规范将请求阈值限制为至少 60 秒。
前述的权限要求也有助于缓解该接口可用于构建用户与设备互动时间画像的普遍担忧。
3.3. 用户强制授权
网站可能要求用户 授权 "idle-detection" 权限才能解锁某些功能。例如,考试网站可能将此权限作为反作弊机制的一部分,检测用户是否在其他窗口查阅了禁止的参考资料。这类“附加合同”现象也出现在通知、FIDO 认证和 DRM 标识等权限上。
一种潜在的缓解方法是设计接口,使网站无法判断用户是 授权还是 拒绝该权限。实现可以拒绝确认用户是否空闲,将网站限制为只能获取现有信号。这种缓解手段可能会被检测出来,因为不太可能有用户长时间未与页面互动却仍然与其他内容持续交互。实现还可以插入模拟空闲切换事件,使其符合页面可用的其他信号下的合理行为。
本规范并不强制采用此类缓解措施,因为它可能导致网站根据虚假数据采取行动时产生糟糕的用户体验。例如,之前提到的消息应用可能不会向用户的移动设备推送通知,因为它认为收到的信号表示用户仍在桌面端。由于网站无法检测自己处于这种状态,无法直接建议用户采取行动来摆脱该状态。
此类网站造成的损害有限,因为只有用户访问该页面时才可能被跟踪。跨多个来源的跟踪需要每个参与网站都发起权限请求。
4. 可访问性注意事项
本节为非规范性内容。
有身体或认知障碍的用户可能需要更多时间与用户代理和内容交互。实现不应允许区分此类用户,也不应比现有 UI 事件观察进一步限制他们与内容的交互能力。例如,应该确保辅助技术的交互也计入用户为活跃状态。
使用权限也要求用户代理提供用于请求和管理该权限的用户界面元素。任何此类界面都必须以无障碍工具为设计考量。例如,描述请求能力的界面应为屏幕阅读器等工具提供相同的说明。
5. 国际化注意事项
本节为非规范性内容。
本规范所述接口的国际化相关内容有限,但使用权限要求用户代理提供界面元素以支持权限请求和管理。用户代理在此场景下展示的任何内容都应翻译为用户的母语。
6. 致谢
本节为非规范性内容。
特别感谢 Kenneth Christiansen、 Samuel Goto、 Ayu Ishii 以及 Thomas Steiner 在本提案制定过程中给予的帮助。