WebXR 设备 API

W3C 候选推荐草案,

关于本文档的更多信息
该版本:
https://www.w3.org/TR/2025/CRD-webxr-20250930/
最新发布版本:
https://www.w3.org/TR/webxr/
编辑草案:
https://immersive-web.github.io/webxr/
以往版本:
历史记录:
https://www.w3.org/standards/history/webxr/
实现报告:
https://wpt.fyi/results/webxr?label=master&label=experimental&aligned
反馈:
GitHub
编辑者:
(Google)
(Google [Mozilla 至 2020])
(Meta)
前任编辑:
(Amazon [Microsoft 至 2018])
参与:
提交议题 (开放议题)
邮件列表存档
W3C 的 #immersive-web IRC

摘要

本规范描述了在 Web 上访问虚拟现实(VR)和增强现实(AR)设备(包括传感器和头戴显示器)的支持。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 发布的文档列表以及本技术报告的最新修订版可在 W3C 标准与草案索引中找到。

沉浸式 Web 工作组维护了尚未处理的所有错误报告列表。本草案突出了一些待解决、仍需在工作组讨论的问题。这些问题的处理结果(包括其是否有效)尚未做出决定。 欢迎针对未解决问题提交包含规范文本建议的拉取请求。

本文档由沉浸式 Web 工作组依据 推荐标准流程 发布为候选推荐草案。本文档计划成为 W3C 推荐标准。

作为候选推荐发布,并不意味着 W3C 及其成员的认可。 候选推荐草案整合了工作组打算纳入后续候选推荐快照的前一版候选推荐的变更。 本文档为草案,可能随时被更新、替换或废止。 引用本文件时仅可视为正在进行中的工作。

本文档进入建议推荐阶段的准入标准是至少有两个独立且可互操作的用户代理实现了本规范的全部功能(通过工作组开发的测试套件中的用户代理测试)。工作组将准备实现报告以跟踪进展。

本文档由遵循 W3C 专利政策的工作组制作。 W3C 维护了与本组成果相关的专利公开列表,该页面也包含专利披露的说明。任何知晓某专利且认为其包含 必要声明 的个人,必须根据 W3C 专利政策第6节披露相关信息。

本文档受2025年8月18日 W3C 流程文件管理。

有关自上一个草案以来的更改, 请参见更改章节。

1. 简介

支持虚拟现实(VR)和增强现实(AR)应用的硬件现在已广泛面向消费者,带来了沉浸式的计算平台,既有新的机遇,也带来了挑战。能够直接与沉浸式硬件交互对于确保 Web 能够作为该环境中的一等公民运行至关重要。

沉浸式计算对高精度、低延迟通信提出了严格要求,以提供可接受的体验。同时,这也为 Web 这样的平台带来了独特的安全性问题。WebXR 设备 API 提供了必要的接口,使开发者能够在各种硬件形态下,在 Web 上创建引人入胜、舒适且安全的沉浸式应用。

其他 Web 接口,如 RelativeOrientationSensorAbsoluteOrientationSensor ,可被用于从部分设备获取输入,在有限的场景下对 WebXR 设备 API 做兼容填补。然而,这些接口无法支持高端沉浸式体验的多种特性,例如6DoF跟踪、对头显外设的内容呈现或受跟踪的输入设备。

1.1. 术语

本文档中使用缩写 XR 以指代用于虚拟现实、增强现实及其他相关技术的硬件、应用和技术手段。示例包括但不限于:

它们的共同点在于都提供一定程度的空间跟踪,用于模拟虚拟内容的视图。

如“XR 设备”、“XR 应用”等术语一般指上述任意设备。仅适用于设备子集的内容将会有明确说明。

术语 3DoF6DoF 在本文档中用于描述XR 设备的跟踪能力。

1.2. 应用流程

大多数使用 WebXR 设备 API 的应用会遵循类似的使用模式:

2. 模型

2.1. XR 设备

XR 设备 是能够向用户呈现沉浸式内容的物理硬件单元。只要内容能够产生视觉、音频、触觉或其他感官输出,以模拟或增强用户环境的各个方面,即可认为是“沉浸式”的。最常见的情况是跟踪用户在空间中的移动,并生成与用户动作同步的输出。在桌面客户端上,这通常是头戴式显示器外设;在移动端,则可能是配合查看器支架使用的移动设备本身。它也可以是没有立体呈现能力但拥有更高级跟踪功能的设备。

XR 设备有一个支持模式列表list of strings),该列表包含XRSessionMode 的枚举值(即该XR 设备支持的模式)。

每个XR 设备对于其支持模式列表中的每个XRSessionMode 都有一个已授予功能集,即有序集合,包含功能描述符,且初始为一个空集合

用户代理有一个沉浸式 XR 设备列表list of XR 设备),初始为一个空列表

用户代理有一个沉浸式 XR 设备nullXR 设备),初始为null,代表沉浸式 XR 设备列表中的活动设备。此对象可以存在于独立线程并异步更新。

用户代理必须有一个默认内嵌 XR 设备,即一个XR 设备,其支持模式列表中必须包含"inline"默认内嵌 XR 设备不能报告任何位姿信息,也不能报告XR 输入源或除指针事件外的其他事件。

注意:默认内嵌 XR 设备仅用于为开发者提供便利,使其能够对内嵌和沉浸内容使用相同的渲染和输入逻辑。默认内嵌 XR 设备不会暴露任何页面其他机制(如用于输入的指针事件)未能获取的信息,只是以 XR 为中心的格式呈现这些值。

用户代理必须有一个内嵌 XR 设备,即一个XR 设备,其支持模式列表中必须包含"inline"内嵌 XR 设备可以是沉浸式 XR 设备(如果其跟踪能力适合暴露给内嵌内容),否则为默认内嵌 XR 设备

注意:在手机上,内嵌 XR 设备可以报告来自手机内部传感器(如陀螺仪和加速度计)的位姿信息。在没有类似传感器的台式机和笔记本电脑上,内嵌 XR 设备无法报告位姿,因此应回退到默认内嵌 XR 设备。如果用户代理本身运行在XR 设备上,内嵌 XR 设备即为同一设备,并可能支持多个视图。用户必须同意后,才可启用除默认内嵌 XR 设备外的任何跟踪或输入功能。

当前沉浸式 XR 设备列表内嵌 XR 设备沉浸式 XR 设备的值可以存在于独立线程并异步更新。这些对象不应在未并行运行的步骤中直接访问。

3. 初始化

partial interface Navigator {
  [SecureContext, SameObject] readonly attribute XRSystem xr;
};

xr 属性的 getter 必须返回与其关联的 XRSystem 对象。

3.2. XRSystem

[SecureContext, Exposed=Window] interface XRSystem : EventTarget {
  // Methods
  Promise<boolean> isSessionSupported(XRSessionMode mode);
  [NewObject] Promise<XRSession> requestSession(XRSessionMode mode, optional XRSessionInit options = {});

  // Events
  attribute EventHandler ondevicechange;
};

当创建 XRSystem 对象时,用户代理必须在创建 Navigator 对象时一同创建,并将其关联。

XRSystem 对象是该 API 的入口点,用于查询用户代理可用的 XR 功能,并通过创建 XRSession 与 XR 硬件建立通信。

用户代理必须能够枚举已连接系统的沉浸式 XR 设备,每次枚举时将每个可用设备加入沉浸式 XR 设备列表。后续请求枚举的算法必须重用缓存的沉浸式 XR 设备列表。枚举设备时不应初始化设备跟踪。首次枚举后,用户代理必须持续监测设备的连接与断开,将已连接设备添加到沉浸式 XR 设备列表,断开设备则移除。

每当沉浸式 XR 设备列表发生变化时,用户代理应通过执行以下步骤选择沉浸式 XR 设备

  1. oldDevice沉浸式 XR 设备

  2. 如果沉浸式 XR 设备列表为空列表,将沉浸式 XR 设备设为null

  3. 如果沉浸式 XR 设备列表长度为 1,则将沉浸式 XR 设备设为沉浸式 XR 设备列表[0]。

  4. 按如下方式设置沉浸式 XR 设备

    若存在任何活动的XRSession,且沉浸式 XR 设备列表包含 oldDevice

    沉浸式 XR 设备设为 oldDevice

    否则:

    沉浸式 XR 设备设置为用户代理自行选择的设备。

  5. 如适用,用户代理可以将内嵌 XR 设备更新为沉浸式 XR 设备,否则为默认内嵌 XR 设备

  6. 如果这是首次枚举设备,或 oldDevice 等于沉浸式 XR 设备,则中止这些步骤。

  7. 关闭所有活动的XRSession

  8. 排队一个任务,将所有WebGLRenderingContextBase实例的XR compatible布尔值设为false

  9. 排队一个任务,在相关全局对象navigatorxr上,派发一个名为devicechange的事件。

  10. 排队一个任务,在受沉浸式 XR 设备内嵌 XR 设备变更影响的XRPermissionStatus对象上派发合适的change事件。

注意:这些步骤应始终并行执行。

注意:沉浸式 XR 设备列表包含多个设备时,用户代理可以按照任何标准选择沉浸式 XR 设备。例如,用户代理可以始终选择列表中的第一个设备,或提供设置界面让用户管理设备优先级。理想情况下,选择默认设备的算法应当稳定,并能在多次浏览会话中选中同一设备。

用户代理可以通过执行以下步骤确保已选择沉浸式 XR 设备

  1. 如果沉浸式 XR 设备不为null,则返回沉浸式 XR 设备并中止这些步骤。

  2. 枚举沉浸式 XR 设备

  3. 选择沉浸式 XR 设备

  4. 返回沉浸式 XR 设备

注意:这些步骤应始终并行执行。

ondevicechange 属性是 事件处理 IDL 属性,用于 devicechange 事件类型。

isSessionSupported(mode) 方法用于查询用户代理和设备能力是否可能支持给定的 mode

调用该方法时,必须执行以下步骤:

  1. promise 为该 XRSystem相关 realm中的新 Promise

  2. 如果 mode"inline"resolve promisetrue 并返回。

  3. 如果请求文档的origin未被允许使用 "xr-spatial-tracking" 权限策略reject promise,异常为 "SecurityError" DOMException ,并返回。

  4. 按如下方式检查会话 mode 是否受支持:

    如果用户代理和系统已知永不支持 mode 会话

    resolve promisefalse

    如果用户代理和系统已知通常支持 mode 会话

    只要所有 UA 字符串不可区分的实例都产生相同结果,promise可以resolvetrue

    否则

    按如下步骤并行执行:

    1. device确保已选择沉浸式 XR 设备 的结果。

    2. 如果 device 为 null,resolve promisefalse,并中止这些步骤。

    3. 如果 device支持模式列表包含 mode排队一个任务resolve promisefalse,并中止这些步骤。

    4. 请求权限使用强大特性 "xr-session-supported" ,权限描述符为 XRSessionSupportedPermissionDescriptormode 值为 mode。如返回"denied"排队一个任务resolve promisefalse,并中止这些步骤。详见指纹识别考量

    5. 排队一个任务resolve promisetrue

  5. 返回 promise

注意:isSessionSupported() 的目的并不是精确报告用户代理是否可以创建 XRSession,而是告知页面是否建议展示可创建给定模式会话的能力。即便用户代理在解析方法前检查了所需硬件/软件的存在,仍可能有一定程度的误报(比如硬件虽然存在,但在会话请求时已被其他应用独占)。

预期大多数含 XR 内容的页面会在文档生命周期早期调用 isSessionSupported()。因此调用 isSessionSupported() 应避免显示模态或其他打扰性 UI。调用 isSessionSupported() 不得触发设备选择 UI,不得干扰系统上正在运行的 XR 应用,也不得引发 XR 相关应用(如系统托盘或商店)的启动。

下例用于检测是否支持 immersive-vr 会话。
const supported = await navigator.xr.isSessionSupported('immersive-vr');
if (supported) {
  // 可能支持 'immersive-vr' 会话。
  // 页面应向用户展示支持信息。
} else {
  // 不支持 'immersive-vr' 会话。
}

XRSystem 对象有一个待处理沉浸式会话布尔值,初始为false,一个活动沉浸式会话,初始为 null,以及一个内嵌会话列表,初始为空。

requestSession(mode, options) 方法在可能时尝试为给定 mode 初始化一个 XRSession,如有需要进入沉浸式模式。

调用该方法时,用户代理必须执行以下步骤:

  1. promise 为该 XRSystem相关 realm中的新 Promise

  2. mode沉浸式会话模式,则 immersivetrue,否则为 false

  3. global object 为调用该方法的 XRSystem相关全局对象

  4. 按如下方式检查会话请求是否被允许:

    immersivetrue
    1. 检查 global object 是否被允许请求沉浸式会话,如不允许则reject promise,异常为 "SecurityError" DOMException,并返回 promise

    2. 待处理沉浸式会话true活动沉浸式会话不为 null,则reject promise,异常为 "InvalidStateError" DOMException,并返回 promise

    3. 待处理沉浸式会话设为 true

    否则:

    检查 global object 是否被允许请求内嵌会话,如不允许则reject promise,异常为 "SecurityError" DOMException,并返回 promise

  5. 按如下步骤并行执行:

    1. requiredFeaturesoptionsrequiredFeatures

    2. optionalFeaturesoptionsoptionalFeatures

    3. device 设为获取当前设备的结果,参数为 moderequiredFeaturesoptionalFeatures

    4. 排队一个任务执行如下步骤:

      1. 如果 devicenulldevice支持模式列表包含 mode,则:

        1. reject promise,异常为 "NotSupportedError" DOMException

        2. immersivetrue,将待处理沉浸式会话设为 false

        3. 中止这些步骤。

      2. descriptor 为用 moderequiredFeaturesoptionalFeatures 初始化的 XRPermissionDescriptor

      3. statusXRPermissionStatus,初始为 null

      4. 请求 xr 权限,参数为 descriptorstatus

      5. 如果 statusstate"denied",则:

        1. reject promise,异常为 "NotSupportedError" DOMException

        2. immersivetrue,将待处理沉浸式会话设为 false

        3. 中止这些步骤。

      6. grantedstatusgranted 得到的集合

      7. session 为该 XRSystem相关 realm中新建的 XRSession 对象。

      8. sessionmodegranteddevice 初始化会话

      9. 按如下方式可选地设置活动沉浸式会话

        immersivetrue

        活动沉浸式会话设为 session,并将待处理沉浸式会话设为 false

        否则:

        session 添加到内嵌会话列表

      10. resolve promise,值为 session

      11. 排队一个任务执行如下步骤:

        注意:这些步骤确保首次 inputsourceschange 事件在初始会话 resolve 后发生。
        1. 设置 sessionpromise 已 resolve 标志为 true

        2. sourcessession 已附加的所有输入源。

        3. sources 非空,执行如下步骤:

          1. 设置 session活动 XR 输入源列表sources

          2. session 上派发名为 inputsourceschangeXRInputSourcesChangeEvent,其 added 设为 sources

  6. 返回 promise

获取当前设备,针对 XRSessionModemoderequiredFeaturesoptionalFeatures,用户代理必须执行:

  1. 按如下方式选择 device

    mode沉浸式会话模式:

    device 设为确保已选择沉浸式 XR 设备 的结果。

    否则如果 requiredFeaturesoptionalFeatures 非空:

    device 设为内嵌 XR 设备

    否则:

    device 设为默认内嵌 XR 设备

  2. 返回 device

注意:这些步骤应始终并行执行。

下例尝试获取一个 immersive-vr XRSession
const xrSession = await navigator.xr.requestSession("immersive-vr");

3.3. XRSessionMode

XRSessionMode 枚举定义了 XRSession 可运行的模式。

enum XRSessionMode {
  "inline",
  "immersive-vr",
  "immersive-ar"
};

在本文档中,inline session 一词等同于 inline 会话,immersive session 指的是 immersive-vrimmersive-ar 会话。

沉浸式会话必须提供某种程度的观察者跟踪,并且内容必须以相对于用户和/或周围环境的正确比例进行显示。此外,沉浸式会话必须获得对沉浸式 XR 设备独占访问,即在沉浸式会话处于"visible"状态时,HTML 文档不会显示在沉浸式 XR 设备的显示上,也不会有其他来源的内容获得独占访问权限。独占访问不会阻止用户代理叠加自己的 UI,但此类 UI 应尽量简洁。

注意:UA 可以为辅助功能或安全性目的叠加内容,例如守护边界、障碍物或在没有其他输入源时显示用户的手部。

注意:未来的规范或模块可能会扩展沉浸式会话,包括更多会话模式。

注意: 独占访问的展现方式示例包括在虚拟现实头显上显示立体内容等。

注意:作为叠加 UI 的示例,用户代理或操作系统在沉浸式会话中可能会在渲染内容上方显示通知。

注意:沉浸式会话期间,HTML 文档不会显示在沉浸式 XR 设备的显示器上,但仍可能显示在其他屏幕上,例如用户从连接到沉浸式 XR 设备的电脑的 2D 浏览器进入沉浸式会话时。

3.4. 特性依赖

XRSession 的某些特性可能因多种原因无法通用提供,其中包括并非所有 XR 设备都能支持全部特性。另一个考量是部分特性会暴露敏感信息,这可能需要明确的用户意图信号后才能启用。

由于初始化底层 XR 平台并创建 XRSession 后又立刻告知用户应用无法正常运行是糟糕的用户体验,开发者可通过向 XRSessionInit 字典传递必需特性,用于 requestSession()。如果由于设备限制或未获得用户意图信号,导致任一必需特性不可用,则会阻止创建 XRSession

此外,鼓励开发者设计在功能更强大设备上能渐进增强体验的应用。对于不是必须但可用时会加以利用的可选特性,也必须在 XRSessionInit 字典中指明,以确保必要时能在启用前获取用户意图

dictionary XRSessionInit {
  sequence<DOMString> requiredFeatures;
  sequence<DOMString> optionalFeatures;
};

requiredFeatures 数组包含了该体验的所有必需特性。如果列表中有任何值不是合法的特性描述符,则不会创建 XRSession。如果 requiredFeatures 列表中的任何特性不被XR 设备支持,或如有必要,未获得用户意图信号,也不会创建 XRSession

optionalFeatures 数组包含该体验的所有可选特性。如果列表中有任何值不是合法的特性描述符,则会被忽略。只要XR 设备支持,且如有必要已获得用户意图信号,optionalFeatures 列表中的特性将被启用,但如缺失不会阻止 XRSession 创建。

如特性列表中给定的值属于以下情况,则认为是合法的特性描述符

该规范的后续版本及其他模块可能会扩展可接受的特性描述符列表。

注意:如某特性需要额外初始化,应扩展 XRSessionInit,为该特性加新字段。

根据请求的 XRSessionMode,某些特性描述符会默认添加到 requiredFeaturesoptionalFeatures 列表中。下表描述了每种会话类型和特性列表相关的默认特性

特性 会话 列表
"viewer" 内嵌会话沉浸式会话 requiredFeatures
"local" 沉浸式会话 requiredFeatures

requiredFeaturesoptionalFeatures 共同给定的特性描述符列表统称为某 XRSession请求特性

部分特性描述符,只要出现在请求特性列表中,需遵循权限策略,且/或要求对该特性的使用已充分理解用户意图,可通过显式同意隐式同意。下表描述了启用前需满足的特性要求

特性 是否需要权限策略 是否需要同意
"local" "xr-spatial-tracking" 内嵌会话 需要同意
"local-floor" "xr-spatial-tracking" 始终需要同意
"bounded-floor" "xr-spatial-tracking" 始终需要同意
"unbounded" "xr-spatial-tracking" 始终需要同意

注意:"local" 总是作为沉浸式会话请求特性之一被包含,作为默认特性,因此沉浸式会话总是需要获得显式同意隐式同意

请求特性只有在XR 设备具备支持能力时才能为会话启用,也就是该特性已知在该XR 设备的某些配置中受支持,即使当前配置尚未确认支持该特性。用户代理可自行决定施加更严格约束,以提供更一致的用户体验。

注意:例如,部分 VR 设备既可配置用户活动边界,也可跳过边界配置以原地操作。这类设备即便当前未配置安全边界,也可视为"bounded-floor" XRReferenceSpace具备支持能力,因为如有需要用户可配置设备以支持体验。这允许用户代理在需要时避免完全初始化XR 设备,或在解析请求特性前等待用户环境被识别。但如果用户代理在会话请求时已知边界状态,则可在安全边界未配置时拒绝 "bounded-floor" 特性。

4. 会话

4.1. XRSession

所有与 XR 硬件的交互都通过 XRSession 对象完成,只能通过在 XRSystem 对象上调用 requestSession() 获取。一旦成功获取会话,可用于 轮询观察者姿态, 查询用户环境信息,以及向用户呈现图像。

用户代理在可能的情况下,不应初始化设备跟踪或渲染能力,直到获取了 XRSession。这样可避免在用户未实际使用 XR 时触发 XR 系统带来的副作用,如电池消耗增加,或首次访问页面时仅测试 XR 硬件存在性便弹出相关工具应用。但不是所有 XR 平台都能在不初始化跟踪的情况下检测硬件是否存在,因此这只是强烈建议。

enum XRVisibilityState {
  "visible",
  "visible-blurred",
  "hidden",
};

[SecureContext, Exposed=Window] interface XRSession : EventTarget {
  // Attributes
  readonly attribute XRVisibilityState visibilityState;
  readonly attribute float? frameRate;
  readonly attribute Float32Array? supportedFrameRates;
  [SameObject] readonly attribute XRRenderState renderState;
  [SameObject] readonly attribute XRInputSourceArray inputSources;
  [SameObject] readonly attribute XRInputSourceArray trackedSources;
  readonly attribute FrozenArray<DOMString> enabledFeatures;
  readonly attribute boolean isSystemKeyboardSupported;

  // Methods
  undefined updateRenderState(optional XRRenderStateInit state = {});
  Promise<undefined> updateTargetFrameRate(float rate);
  [NewObject] Promise<XRReferenceSpace> requestReferenceSpace(XRReferenceSpaceType type);

  unsigned long requestAnimationFrame(XRFrameRequestCallback callback);
  undefined cancelAnimationFrame(unsigned long handle);

  Promise<undefined> end();

  // Events
  attribute EventHandler onend;
  attribute EventHandler oninputsourceschange;
  attribute EventHandler onselect;
  attribute EventHandler onselectstart;
  attribute EventHandler onselectend;
  attribute EventHandler onsqueeze;
  attribute EventHandler onsqueezestart;
  attribute EventHandler onsqueezeend;
  attribute EventHandler onvisibilitychange;
  attribute EventHandler onframeratechange;
};

每个 XRSession 有一个 mode,取值为 XRSessionMode 枚举之一。

每个 XRSession 有一个 动画帧,是一个以 active 设为 falseanimationFrame 设为 truesession 设为该 XRSessionXRFrame

每个 XRSession 有一个 已授予功能集,是一个 有序集合,包含已授予该 XRSession特性描述符对应的 DOMString

enabledFeatures 属性返回 已授予功能集中的功能,作为新的 DOMString 数组。

isSystemKeyboardSupported 属性表示 XRSystemXRSession 活动时能否显示系统键盘。如果 isSystemKeyboardSupportedtrue,则触发覆盖键盘的 Web API(如 focus)时会显示系统键盘。当键盘显示时,XRSession 必须将会话的 可见性状态 设为 "visible-blurred"

初始化会话,给定 sessionmodegranteddevice,用户代理必须执行以下步骤:

  1. sessionmode 设为 mode

  2. sessionXR 设备 设为 device

  3. session已授予功能集 设为 granted

  4. 初始化渲染状态

  5. 如用户代理的其他功能尚未执行,按平台相关步骤初始化设备的跟踪和渲染能力,包括向用户显示必要的指引。

注意:部分设备激活前需要用户额外操作。例如,手机头显设备需将手机插入头显,在连接外部头显的桌面浏览器则需佩戴头显。确保相关指引展示是用户代理的责任,而非内容作者。

多种情况可能会关闭会话,该操作是永久且不可逆的。会话关闭后,想再次访问 XR 设备 的跟踪或渲染能力,只能重新请求会话。每个 XRSession 有一个 ended 布尔值,初始为 false,指示是否已关闭。

XRSession session 被关闭时,执行以下步骤:

  1. sessionended 设为 true

  2. 如果 活动沉浸式会话 等于 session,则将 活动沉浸式会话 设为 null

  3. 内嵌会话列表中移除 session

  4. 拒绝 session 返回的所有未完成的 promise,抛出 InvalidStateError ,但 end() 返回的 promise 除外。

  5. 如用户代理没有其他功能仍在用,按平台相关步骤关闭设备的跟踪和渲染能力,必须包括:

  6. 排队一个任务,在 session 上派发名为 XRSessionEventend 事件。

end() 方法用于手动关闭会话。调用时,必须执行以下步骤:

  1. promise 为该 XRSession相关 realm 中的 新 Promise

  2. 如果 endedtruereject promise,抛出 "InvalidStateError" DOMException 并返回 promise

  3. 关闭 this

  4. 排队一个任务,执行下列步骤:

    1. 等待所有与关闭会话相关的平台特定步骤完成。

    2. resolve promise

  5. 返回 promise

每个 XRSession 有一个 活动渲染状态,即一个新的 XRRenderState,以及一个 待处理渲染状态,最初为 nullXRRenderState

renderState 属性返回 XRSession活动渲染状态

每个 XRSession最小内嵌视场最大内嵌视场,以弧度为单位。其值必须由用户代理确定,范围在 0PI 之间。

每个 XRSession最小近裁剪面最大远裁剪面,以米为单位。其值必须由用户代理确定且为非负数。最小近裁剪面应小于 0.1最大远裁剪面应大于 1000.0(也可为无穷大)。

当用户代理要用 XRSession sessionXRRenderStateInit newState 更新待处理图层状态时,必须执行以下步骤:

  1. newStatelayers 不为 null,则抛出 NotSupportedError

注: WebXR 图层模块将为该算法引入新语义。

当用户代理要在 XRSession session应用名义帧率 rate 时,必须执行以下步骤:

  1. 如果 ratesession内部名义帧率相同,则中止这些步骤。

  2. 如果 sessionendedtrue,中止这些步骤。

  3. session内部名义帧率设为 rate

  4. session 上派发名为 XRSessionEventframeratechange 事件。

updateTargetFrameRate(rate) 方法将 目标帧率 rate 传递给 XRSession

调用该方法时,用户代理必须执行以下步骤:

  1. sessionthis

  2. promisesession相关 realm 中的 新 Promise

  3. 如果 session 没有 内部名义帧率,则 reject promise,抛出 "InvalidStateError" DOMException 并返回 promise

  4. 如果 sessionendedtrue,则 reject promise,抛出 "InvalidStateError" DOMException 并返回 promise

  5. 如果 rate 不在 supportedFrameRates 中,reject promise,抛出 "TypeError" DOMException 并返回 promise

  6. session内部目标帧率设为 rate

  7. 排队一个任务,执行下列步骤:

    1. XR 合成器可用 rate 计算新的 显示帧率 和/或 名义帧率

    2. newrate 为新的 名义帧率

    3. 再排队一个任务,执行下列步骤:

      1. 等待 XRSystem 的更新 名义帧率newrate 的操作生效。

      2. 应用名义帧率,参数为 newratesession

      3. resolve promise

  8. 返回 promise

如果 XR 合成器因任何原因更改了名义帧率(例如在 "visible-blurred" 事件期间),则该事件结束后应继续使用 内部目标帧率

updateRenderState(newState) 方法会排队更新活动渲染状态,将在下一帧应用。传入方法的 XRRenderStateInit newState 未设置的字段不会被更改。

调用时,用户代理必须执行以下步骤:

  1. sessionthis

  2. 如果 sessionendedtrue,抛出 InvalidStateError,中止这些步骤。

  3. 如果 newStatebaseLayer 是用非 session 创建的 XRSession,抛出 InvalidStateError,中止这些步骤。

  4. 如果 newStateinlineVerticalFieldOfView 已设置,且 session沉浸式会话,抛出 InvalidStateError,中止这些步骤。

  5. 如果 newStatedepthNeardepthFarinlineVerticalFieldOfViewbaseLayerlayers 都未设置,中止这些步骤。

  6. sessionnewState 更新待处理图层状态

  7. activeStatesession活动渲染状态

  8. 如果 session待处理渲染状态null,则将其设为 activeState 的副本。

  9. 如果 newStatepassthroughFullyObscured 已设置,则将 session待处理渲染状态passthroughFullyObscured 设为 newState 的相应值。

  10. 如果 newStatedepthNear 已设置,则将 session待处理渲染状态depthNear 设为 newState 的相应值。

  11. 如果 newStatedepthFar 已设置,则将 session待处理渲染状态depthFar 设为 newState 的相应值。

  12. 如果 newStateinlineVerticalFieldOfView 已设置,则将 session待处理渲染状态inlineVerticalFieldOfView 设为 newState 的相应值。

  13. 如果 newStatebaseLayer 已设置,则将 session待处理渲染状态baseLayer 设为 newState 的相应值。

当被请求时,XRSession session 必须通过执行以下步骤来应用待处理渲染状态

  1. activeStatesession活动渲染状态

  2. newStatesession待处理渲染状态

  3. session待处理渲染状态 设为 null

  4. oldBaseLayeractiveStatebaseLayer

  5. oldLayersactiveStatelayers

  6. 排队一个任务,执行以下步骤:

    1. activeState 设为 newState

    2. 如果 oldBaseLayer 不等于 activeStatebaseLayeroldLayers 不等于 activeStatelayers, 或任何图层的尺寸发生了变化,则为 session 更新视口

    3. 如果 activeStateinlineVerticalFieldOfView 小于 session最小内嵌视场,则将 activeStateinlineVerticalFieldOfView 设为 session最小内嵌视场

    4. 如果 activeStateinlineVerticalFieldOfView 大于 session最大内嵌视场,则将 activeStateinlineVerticalFieldOfView 设为 session最大内嵌视场

    5. 如果 activeStatedepthNear 小于 session最小近裁剪面,则将 activeStatedepthNear 设为 session最小近裁剪面

    6. 如果 activeStatedepthFar 大于 session最大远裁剪面,则将 activeStatedepthFar 设为 session最大远裁剪面

    7. baseLayeractiveStatebaseLayer

    8. 按如下方式设置 activeStatecomposition enabledoutput canvas

      如果 sessionmode"inline"baseLayerXRWebGLLayer 的实例,且其 composition enabled 设为 false

      activeStatecomposition enabled 设为 false

      activeStateoutput canvas 设为 baseLayercontextcanvas

      否则:

      activeStatecomposition enabled 设为 true

      activeStateoutput canvas 设为 null

requestReferenceSpace(type) 方法用于根据给定的 type 构造一个新的 XRReferenceSpace,如果可能的话。

调用该方法时,用户代理必须执行以下步骤:

  1. promise 为该 XRSession相关 realm 中的 新 Promise

  2. 按如下步骤 并行执行:

    1. 如果对 typesession 执行 reference space is supported 结果为 false排队一个任务reject promise,异常为 NotSupportedError,并中止这些步骤。

    2. 设置跟踪 type 类型参考空间所需的任何平台资源。

      用户代理不需要等待此类参考空间的跟踪建立后再 resolve requestReferenceSpace()。在会话最初尝试建立跟踪时,getViewerPose() 返回 null 是可以的,内容可在此期间显示启动画面等。注意若 type"bounded-floor" 且边界尚未建立,用户代理可以将边界设为一个较小的初始区域,并在边界建立时通过 reset 事件通知。
    3. 排队一个任务,执行以下步骤:

    4. 创建参考空间 referenceSpace,参数为 typesession

    5. resolve promise,值为 referenceSpace

  3. 返回 promise

每个 XRSession 有一个 活动 XR 输入源列表list of XRInputSource)和一个 活动 XR 跟踪源列表list of XRInputSource),两者初始都为空列表

每个 XRSession 有一个 XR 设备,该设备在初始化时设置。

inputSources 属性返回 XRSession活动 XR 输入源列表

trackedSources 属性返回 XRSession活动 XR 跟踪源列表

用户代理必须监控与 XR 设备 相关的所有 XR 输入源,包括检测 XR 输入源 的新增、移除或更改。

每个 XRSession 有一个 promise 已 resolve 标志,初始为 false

注: 该标志用于确保 添加输入源移除输入源更改输入源算法不会运行,直到用户代码有机会绑定事件监听器。如果实现选择在会话 resolve 后才开始监听输入源变化,则可能不需要该标志。

有新的 XR 输入源 可用XRSession session 时,用户代理必须执行以下步骤:

  1. 如果 sessionpromise 已 resolve 标志 未设置,则中止这些步骤。

  2. added primary sources 为一个新的 列表

  3. added tracked sources 为一个新的 列表

  4. 对每个新的 XR 输入源

    1. inputSource 为该 XRSession相关 realm 中的 XRInputSource,然后:

      如果 inputSource主输入源

      inputSource 添加到 added primary sources

      否则:

      inputSource 添加到 added tracked sources

  5. 排队一个任务,执行以下步骤:

    1. 扩展 session活动 XR 输入源列表,添加 added primary sources

    2. 如果 added primary sources 非空,在 session 上派发名为 XRInputSourcesChangeEventinputsourceschange 事件,added 设为 added primary sources

    3. 扩展session活动 XR 跟踪源列表,将 added tracked sources 添加进去。

    4. 如果 added tracked sources 非空,在 session 上派发名为 XRInputSourcesChangeEventtrackedsourceschange 事件,added 设为 added tracked sources

当之前添加的XR 输入源不再可用XRSession session 时,用户代理必须执行以下步骤:

  1. 如果 sessionpromise 已 resolve 标志 未设置,则中止这些步骤。

  2. removed primary sources 为一个新的 列表

  3. removed tracked sources 为一个新的 列表

  4. 对每个不再可用的 XR 输入源

    1. inputSourcesession活动 XR 输入源列表中与该 XR 输入源关联的 XRInputSource,然后:

      如果 inputSource主输入源

      inputSource 添加到 removed primary sources

      否则:

      inputSource 添加到 removed tracked sources

  5. 排队一个任务,执行以下步骤:

    1. 移除 removed primary sources 中的每个 XRInputSource,从 session活动 XR 输入源列表中。

    2. 如果 removed primary sources 非空,在 session 上派发名为 XRInputSourcesChangeEventinputsourceschange 事件,removed 设为 removed primary sources

    3. 移除 removed tracked sources 中的每个 XRInputSource,从 session活动 XR 跟踪源列表中。

    4. 如果 removed tracked sources 非空,在 session 上派发名为 XRInputSourcesChangeEventtrackedsourceschange 事件,removed 设为 removed tracked sources

注意:当输入源暂时失去位置和朝向跟踪时,用户代理可以触发此事件。建议仅对物理手持控制器输入源这样做。对于手部跟踪输入源,由于这种情况频繁发生,不建议这样做;对于跟踪器对象输入源也不推荐这样做,因为这会让应用难以维护身份概念。

handednesstargetRayModeprofiles、 是否存在 gripSpace, 或作为主输入源跟踪输入源的状态 发生变化时,对于 XRSession session,用户代理必须执行以下步骤:

  1. 如果 sessionpromise 已 resolve 标志 未设置,则中止这些步骤。

  2. added primary sources 为一个新的 列表

  3. removed primary sources 为一个新的 列表

  4. added tracked sources 为一个新的 列表

  5. removed tracked sources 为一个新的 列表

  6. 对每个发生变化的 XR 输入源

    1. oldInputSourcesession活动 XR 输入源列表中先前与该 XR 输入源关联的 XRInputSource,然后:

      如果 oldInputSource主输入源,或其状态由主输入源变为跟踪输入源

      oldInputSource 添加到 removed primary sources

      否则:

      oldInputSource 添加到 removed tracked sources

    2. newInputSourcesession相关 realm 中的 XRInputSource,然后:

      如果 newInputSource主输入源,或其状态由跟踪输入源变为主输入源

      newInputSource 添加到 added primary sources

      否则:

      newInputSource 添加到 added tracked sources

  7. 排队一个任务,执行以下步骤:

    1. 移除 removed primary sources 中的每个 XRInputSource,从 session活动 XR 输入源列表中。

    2. 扩展 session活动 XR 输入源列表,添加 added primary sources

    3. 如果 added primary sourcesremoved primary sources 非空,在 session 上派发名为 XRInputSourcesChangeEventinputsourceschange 事件,added 设为 added primary sourcesremoved 设为 removed primary sources

    4. 移除 removed tracked sources 中的每个 XRInputSource,从 session活动 XR 跟踪源列表中。

    5. 扩展 session活动 XR 输入源列表,添加 added tracked sources

    6. 如果 added tracked sourcesremoved tracked sources 非空,在 session 上派发名为 XRInputSourcesChangeEventtrackedsourceschange 事件,added 设为 added tracked sourcesremoved 设为 removed tracked sources

每个 XRSession 都有一个 visibility state(可见性状态) 值,是一个枚举。对于 内嵌会话可见性状态 必须与 DocumentvisibilityState 保持一致。对于 沉浸式会话可见性状态 必须设置为下列最能反映会话状态的值之一。

visibilityState 属性返回 XRSession可见性状态onvisibilitychange 属性是 事件处理 IDL 属性,用于 visibilitychange 事件类型。

可见性状态 可以在除 XR 动画帧 处理期间外的任何时间被用户代理更改,并且用户代理在可能时应监控 XR 平台以观察会话可见性受外部影响时及时更新 可见性状态

注意: XRSession可见性状态 并不代表 HTML 文档的可见性。根据系统配置,页面在 沉浸式会话 活动时可能仍然可见。(例如,连接到 PC 的头显在头显显示沉浸式内容时,显示器上页面仍可见。)开发者应继续依赖 Page Visibility 判断页面可见性。

注意: XRSession可见性状态 不影响或限制在有线连接(2D 内容仍可见)下的鼠标行为。内容如需更强的鼠标行为控制,可考虑使用 [pointerlock] API。

XRSystem 中,有几种描述帧率的定义:

每个 XRSession 可有一个 internal target frameRate(内部目标帧率),即 目标帧率

每个 XRSession 可有一个 internal nominal frameRate(内部名义帧率),即 名义帧率。如果 实际帧率 低于 名义帧率XR 合成器 可以用重投影等技术提升体验。inline 会话不可有该属性。

frameRate 属性反映 内部名义帧率。若 XRSession内部名义帧率,则返回 null

onframeratechange 属性是 事件处理 IDL 属性,用于 frameratechange 事件类型。若 XRSession名义帧率因任何原因变化,必须用新名义帧率和该 XRSession 应用名义帧率

supportedFrameRates 属性返回支持的 目标帧率值列表。该属性是可选的,且 inline 会话或作者无法控制帧率的 XRSystem 不得有此属性。若 XRSession 支持该属性,也必须支持 frameRate

每个 XRSession 有一个 viewer reference space(观察者参考空间),即类型为 "viewer",带有 单位变换 原点偏移XRReferenceSpace

每个 XRSession 有一个 视图列表,是与 XR 设备 提供的视图对应的 viewlist。如果 XRSessionrenderStatecomposition enabled 布尔值为 false视图列表必须只包含一个 view视图列表XRSession 生命周期内不可变,且必须包含会话期间可能出现的所有 view,包括初始时未激活次视图

onend 属性是 事件处理 IDL 属性,用于 end 事件类型。

oninputsourceschange 属性是 事件处理 IDL 属性,用于 inputsourceschange 事件类型。

onselectstart 属性是 事件处理 IDL 属性,用于 selectstart 事件类型。

onselectend 属性是 事件处理 IDL 属性,用于 selectend 事件类型。

onselect 属性是 事件处理 IDL 属性,用于 select 事件类型。

onsqueezestart 属性是 事件处理 IDL 属性,用于 squeezestart 事件类型。

onsqueezeend 属性是 事件处理 IDL 属性,用于 squeezeend 事件类型。

onsqueeze 属性是 事件处理 IDL 属性,用于 squeeze 事件类型。

4.2. XRRenderState

XRRenderState 表示一组可配置的值,这些值会影响 XRSession 输出的合成方式。给定 XRSession活动渲染状态只能在帧边界之间改变,并可通过 updateRenderState() 方法排队更新。

dictionary XRRenderStateInit {
  double depthNear;
  double depthFar;
  boolean passthroughFullyObscured;
  double inlineVerticalFieldOfView;
  XRWebGLLayer? baseLayer;
  sequence<XRLayer>? layers;
};

[SecureContext, Exposed=Window] interface XRRenderState {
  readonly attribute double depthNear;
  readonly attribute double depthFar;
  readonly attribute boolean? passthroughFullyObscured;
  readonly attribute double? inlineVerticalFieldOfView;
  readonly attribute XRWebGLLayer? baseLayer;
};

每个 XRRenderState 都有一个 output canvas,即 HTMLCanvasElement,初始为 nulloutput canvas 是用于展示 "inline" XRSession 渲染内容的 DOM 元素。

每个 XRRenderState 还有一个 composition enabled 布尔值,初始为 true。当渲染命令针对 API 提供的表面执行,并由 XR 合成器 展示时,XRRenderState 被认为是composition enabled。如果 "inline" XRSession 的渲染直接输出到 output canvas,则 XRRenderStatecomposition enabled 标志必须为 false

注意:目前只有当 composition enabled 设为 false 时,XRRenderState 才有 output canvas;但未来规范版本可能会引入更多高级用法(如镜像、图层合成)下的 output canvas 设定方式,这些将需要开启 composition。

当为 XRRenderState 创建 XRSession session 时,用户代理必须 初始化渲染状态,执行以下步骤:

  1. statesession XRRenderState 对象。

  2. statedepthNear 初始化为 0.1

  3. statedepthFar 初始化为 1000.0

  4. statepassthroughFullyObscured 初始化为 false

  5. 按如下方式初始化 stateinlineVerticalFieldOfView

    如果 session内嵌会话

    初始化为 PI * 0.5

    否则:

    初始化为 null

  6. statebaseLayer 初始化为 null

depthNear 属性定义了近平面距离观察者的距离(米)。depthFar 属性定义了远平面距离观察者的距离(米)。

depthNeardepthFar 会用于 projectionMatrix 计算 XRView 时。渲染时使用 projectionMatrix 时,只有距离观察者depthNeardepthFar 之间的几何体才会被绘制。它们还决定了 XRWebGLLayer 深度缓冲区的取值解释方式。depthNear 可大于 depthFar

注意: 通常构建透视投影矩阵时,开发者会指定视椎体及近平面和远平面。对于沉浸式 XR 设备,视椎体由光学、显示和摄像头等因素共同决定。但近平面和远平面可由应用调整,因为合适的取值取决于渲染的内容类型。

passthroughFullyObscured 属性是作者给 XRSystem 的提示,表示其打算用虚拟内容完全覆盖视口。当视口不再被不透明像素覆盖时,作者应将该标志重新设为 false

注意: XRSystem 可以用此作为临时禁用透视通道的提示。对于透视光学设备,用户仍能看到环境,此标志无效。

inlineVerticalFieldOfView 属性定义了以弧度为单位的默认垂直视场角,用于计算 "inline" XRSession 的投影矩阵。矩阵计算还会考虑output canvas 的宽高比。对于沉浸式会话,该值必须为 null

baseLayer 属性定义了 XRWebGLLayerXR 合成器将从中获取图像。

4.3. 动画帧

XRSession 提供 XR 设备 跟踪状态信息的主要方式,是通过在 XRSession 实例上调用 requestAnimationFrame() 安排回调。

callback XRFrameRequestCallback = undefined (DOMHighResTimeStamp time, XRFrame frame);

每个 XRFrameRequestCallback 对象都有一个 cancelled 布尔值,初始为 false

每个 XRSession 有 一个 动画帧回调列表,初始为空, 一个 当前运行动画帧回调列表,也初始为空, 以及一个 动画帧回调标识符,是一个初始为零的数字。

requestAnimationFrame(callback) 方法会将 callback 加入队列,以便下次用户代理要为设备运行动画帧时调用。

调用此方法时,用户代理必须执行以下步骤:

  1. sessionthis

  2. 如果 sessionended 值为 true,则返回 0,并中止这些步骤。

  3. session动画帧回调标识符加一。

  4. callback 及当前 session动画帧回调标识符 追加到 session动画帧回调列表

  5. 返回 session动画帧回调标识符 当前值。

cancelAnimationFrame(handle) 方法用于根据 动画帧回调标识符 handle 取消已存在的动画帧回调。

调用此方法时,用户代理必须执行以下步骤:

  1. sessionthis

  2. session动画帧回调列表session当前运行动画帧回调列表 中查找与 handle 关联的条目。

  3. 如果找到该条目,将其 cancelled 布尔值设为 true,并从 session动画帧回调列表中移除。

要用 renderState state 检查图层状态,用户代理必须执行以下步骤:
  1. 如果 statebaseLayernull,返回 false

  2. 返回 true

注意: WebXR 图层模块将为该算法引入新语义。

要判断某一帧是否应被渲染,针对 XRSession session,用户代理必须执行以下步骤:
  1. 如果用 sessionrenderState 检查图层状态 返回 false,则返回 false

  2. 如果 sessionmode"inline"sessionrenderStateoutput canvasnull,则返回 false

  3. 返回 true

XRSession sessionXR 设备 接收到时间戳 frameTime观察者状态更新时,会运行一次 XR 动画帧,无论 动画帧回调列表 是否为空,都必须执行以下步骤:

  1. 排队一个任务,执行以下步骤:

    1. now当前高精度时间

    2. framesession动画帧

    3. frametime 设为 frameTime

    4. framepredictedDisplayTime 设为 frameTime

    5. 如果 sessionmode 不是 "inline",将 framepredictedDisplayTime 设为 XR 合成器 预计显示该 XR 动画帧 的平均时间戳。

    6. 视图列表 中每个 view,将其 viewport modifiable 标志设为 true。

    7. 如果 视图列表 中任何 viewactive 标志自上次 XR 动画帧 后发生变化,更新视口

    8. 如果该帧对 session 应被渲染

      1. session当前运行动画帧回调列表 设为 session动画帧回调列表

      2. session动画帧回调列表 设为一个空列表。

      3. frameactive 设为 true

      4. 应用帧更新frame

      5. 按顺序对 session当前运行动画帧回调列表 中的每个 entry

      6. 如果 entrycancelledtrue,则继续下一个 entry。

      7. 调用 entry,参数为 « now, frame » 和 "report"。

      8. session当前运行动画帧回调列表 设为空 列表

      9. frameactive 设为 false

    9. 如果 sessionpending render state 不为 null,则应用待处理渲染状态

Window 接口的 requestAnimationFrame() 方法不会因有活动的 XRSession 而改变,且在任何 XRSession 上调用 requestAnimationFrame() 也不会与 WindowrequestAnimationFrame() 产生任何交互。活动沉浸式会话 可以影响 渲染时机,如果它导致页面被遮挡。如果在 活动沉浸式会话 期间 2D 浏览器视图可见(即会话运行在有线头显上),则 WindowrequestAnimationFrame()requestIdleCallback() 的回调时机可能不会与会话的 requestAnimationFrame() 同步,不应用于渲染 XR 内容。

注意:如果在 XRSessionrequestAnimationFrame() 回调中调用了 WindowrequestAnimationFrame(),用户代理可能会在开发者控制台显示警告,因为如果 活动沉浸式会话 影响 渲染时机,这些回调可能不会被调用,即使调用了时机也可能不准确。

如果 沉浸式会话阻止了渲染时机,则传给 Window requestAnimationFrame() 的回调,在会话活动期间可能不会被处理。这取决于所用设备类型,最有可能出现在移动或一体机设备上,此时沉浸内容会完全遮挡 HTML 文档。因此,开发者不能依赖 Window requestAnimationFrame() 回调来调度 XRSession requestAnimationFrame() 回调,反之亦然,即使它们共享渲染逻辑。未遵循该建议的应用在所有平台上可能无法正常运行。以下演示了希望在两种动画循环间切换的应用更有效的模式:
let xrSession = null;

function onWindowAnimationFrame(time) {
  window.requestAnimationFrame(onWindowAnimationFrame);

  // 这可能在某些设备(如有线头显桌面)运行沉浸式会话时被调用。
  // 为防止两个循环并行渲染,会话未开始时才渲染。
  if (!xrSession) {
    renderFrame(time, null);
  }
}

// 页面加载即可启动 window 动画循环。
window.requestAnimationFrame(onWindowAnimationFrame);

function onXRAnimationFrame(time, xrFrame) {
  xrSession.requestAnimationFrame(onXRAnimationFrame);
  renderFrame(xrFrame.predictedDisplayTime, xrFrame);
}

function renderFrame(time, xrFrame) {
  // 共享渲染逻辑。
}

// 假定由用户手势事件调用。
async function startXRSession() {
  xrSession = await navigator.xr.requestSession('immersive-vr');
  xrSession.addEventListener('end', onXRSessionEnded);
  // 这里做必要的会话设置。
  // 启动会话动画循环。
  xrSession.requestAnimationFrame(onXRAnimationFrame);
}

function onXRSessionEnded() {
  xrSession = null;
}

使用 "inline" 会话渲染到 HTML 文档的应用无需特别协调动画循环,因为用户代理会在 沉浸式会话活动时自动挂起所有 "inline" 会话的动画循环。

4.4. XR 合成器

用户代理必须维护一个 XR 合成器,用于处理 XR 设备的输出展示和帧时序。合成器必须使用独立渲染上下文,其状态与文档创建的任何图形上下文隔离。合成器必须防止页面破坏合成器状态或读取其他页面/应用的内容。合成器还必须运行在独立线程或进程中,使页面性能与向用户适时展示新图像解耦。合成器可以在渲染内容上叠加设备或用户代理 UI,比如设备菜单。

注意:该规范的未来扩展可能允许合成器复合来自同一页面的多个图层。

5. 帧循环

5.1. XRFrame

XRFrame 表示某个 XRSession 的所有被跟踪对象状态的快照。 应用可以通过在 XRSession 上调用 requestAnimationFrame() 并传入 XRFrameRequestCallback 获得 XRFrame。 回调被调用时会传入一个 XRFrame。需要传递跟踪状态的事件(如 select 事件)也会提供 XRFrame

[SecureContext, Exposed=Window] interface XRFrame {
  [SameObject] readonly attribute XRSession session;
  readonly attribute DOMHighResTimeStamp predictedDisplayTime;

  XRViewerPose? getViewerPose(XRReferenceSpace referenceSpace);
  XRPose? getPose(XRSpace space, XRSpace baseSpace);
};

每个 XRFrame 都有一个 active 布尔值,初始为 false,以及一个 animationFrame 布尔值,初始为 false

session 属性返回生成该 XRFrameXRSession

对于 沉浸式会话predictedDisplayTime 属性必须返回该 XRFrame 预计显示在设备上的平均时间点对应的 DOMHighResTimeStamp。对于 "inline" XRSessionpredictedDisplayTime 必须返回传递给 XRFrameRequestCallback 的时间戳值。

predictedDisplayTime 用于让渲染的 XR 场景状态能反映帧实际呈现时的状态,而不是 requestAnimationFrame() 被安排或执行的时刻。

predictedDisplayTime 并非用于推断应用有多少渲染时间,因为 XR 合成器 通常帧提交后还要做额外处理。如果体验假设可以一直处理到 predictedDisplayTime,则 XR 合成器 无法利用已提交帧,应用也无法达到目标帧率。

每个 XRFrame 表示某一 时间点所有被跟踪对象的状态,并存储或能够查询该时间点的具体信息。

getViewerPose(referenceSpace) 方法返回 观察者 相对于 referenceSpace 的姿态(XRViewerPose),其状态为 XRFrametime

调用该方法时,用户代理必须执行以下步骤:

  1. framethis

  2. sessionframesession 对象。

  3. 如果 frameanimationFramefalse,抛出 InvalidStateError 并中止这些步骤。

  4. posesession XRViewerPose 对象。

  5. 填充 session观察者参考空间referenceSpace 下于 frame 表示的时间的姿态到 poseforce emulation 设为 true

  6. 如果 posenull,返回 null

  7. xrviews 为一个空的 列表

  8. offset0

  9. 对于 session视图列表中的每个激活视图 view,执行以下步骤:

    1. xrviewsession相关领域中新建的 XRView 对象。

    2. 初始化 xrview底层视图view

    3. 初始化 xrvieweyeview眼睛

    4. 初始化 xrviewindexoffset

    5. 初始化 xrviewframe

    6. 初始化 xrview会话session

    7. 初始化 xrview参考空间referenceSpace

    8. viewtransformsession相关领域中新建、等于 view视图偏移XRRigidTransform 对象。

    9. xrviewtransform 属性设置为 XRViewerPosetransformviewtransform 变换在 session 的相关领域中相乘的结果。

    10. xrview 添加到 xrviews

    11. offset 增加 1

  10. poseviews 设为 xrviews

  11. 返回 pose

getPose(space, baseSpace) 方法返回 space 相对于 baseSpace 的姿态(XRPose),其状态为 XRFrame 表示的时间。

调用该方法时,用户代理必须执行以下步骤:

  1. framethis

  2. poseframe XRPose 对象。

  3. 填充 spacebaseSpace 下于 frame 表示的时间的姿态到 pose

  4. 返回 pose

帧更新 是一个可以给定 XRFrame 执行的算法,意在每个 XRFrame 被执行时运行。

每个 XRSession 都有一个 帧更新列表,是一个 列表,元素为帧更新,初始为空列表

要为 XRFrame frame 应用帧更新,用户代理必须执行以下步骤:

  1. framesession帧更新列表 中每个 frame update,执行:

    1. frame 运行 frame update

注意:本规范未定义任何帧更新,但其他规范可能会添加。

6. 空间(Spaces)

WebXR Device API 的核心特性之一是能够提供空间跟踪。空间接口使应用能够推理被跟踪实体彼此之间及与用户物理环境的空间关系。

6.1. XRSpace

XRSpace 表示一个虚拟坐标系,其原点对应于一个物理位置。从 API 请求或提供的空间数据始终是相对于特定 XRSpace 及特定 XRFrame 的时刻来表达的。诸如姿态坐标等数值都是该空间内相对于原点的坐标。该接口设计为不可透明访问。

[SecureContext, Exposed=Window] interface XRSpace : EventTarget {

};

每个 XRSpace 有一个 session,其值为创建该 XRSpaceXRSession

每个 XRSpace 有一个 原生原点,即空间中的位置和朝向。该 XRSpace原生原点 可能会被 XR 设备的底层跟踪系统更新,不同的 XRSpace 可以定义其 原生原点如何被跟踪和更新的不同语义。

每个 XRSpace 有 一个 有效原点,其作为 XRSpace坐标系的基础。

从有效空间到原生原点空间的变换由 原点偏移定义, 即一个初始值为单位变换XRRigidTransform。换句话说, 有效原点 可通过原点偏移与原生原点相乘获得。

有效原点 只能作为另一个 XRSpace 坐标系中的 XRPose 被观察到,可通过 XRFramegetPose() 方法获得。不同 XRSpace 之间的空间关系可以在不同 XRFrame 之间发生变化。

填充 姿态(pose),即在 XRSpace space 相对于 XRSpace baseSpace,在由 XRFrame frame 表示的时刻,将结果填入 XRPose pose,可选地带 force emulation 标志,用户代理必须执行以下步骤:

  1. 如果 frameactivefalse,抛出 InvalidStateError 并中止这些步骤。

  2. sessionframesession 对象。

  3. 如果 spacesession 不等于 session,抛出 InvalidStateError 并中止这些步骤。

  4. 如果 baseSpacesession 不等于 session,抛出 InvalidStateError 并中止这些步骤。

  5. 检查能否报告姿态,如不行则抛出 SecurityError 并中止这些步骤。

  6. 如果 sessionvisibilityState"visible-blurred"spacebaseSpaceXRInputSource 关联,则将 pose 设为 null 并中止这些步骤。

  7. limit姿态是否需限制spacebaseSpace 之间)。

  8. transformposetransform

  9. XR 设备 跟踪系统查询 space 相对于 baseSpaceframetime 的姿态,然后按如下分支:

    如果 limitfalse 且跟踪系统为 space 相对于 baseSpace 提供了正在主动跟踪或静态已知的 6DoF 姿态:

    transformorientation 设为 space有效原点baseSpace坐标系 中的朝向。

    transformposition 设为 space有效原点baseSpace坐标系 中的位置。

    如果支持,将 poselinearVelocity 设为 space有效原点 相对 baseSpace 坐标系 的线速度。

    如果支持,将 poseangularVelocity 设为 space有效原点 相对 baseSpace 坐标系 的角速度。

    poseemulatedPosition 设为 false

    否则如果 limitfalse 且跟踪系统为 space 相对于 baseSpace 提供了 3DoF 姿态或者 6DoF 但位置既未主动跟踪也非静态已知:

    transformorientation 设为 space有效原点baseSpace坐标系 中的朝向。

    transformposition 设为跟踪系统对 space有效原点baseSpace 坐标系 中位置的最佳估计。该估计可以包含如脖子或手臂模型等计算偏移。如无可用位置估计,必须使用上一个已知位置。

    poselinearVelocity 设为 null

    poseangularVelocity 设为 null

    poseemulatedPosition 设为 true

    否则如果 space 相对于 baseSpace 的姿态曾被确定且 force emulationtrue

    transformposition 设为 space有效原点baseSpace 坐标系 中的上一次已知位置。

    transformorientation 设为 space有效原点baseSpace 坐标系 中的上一次已知朝向。

    poselinearVelocity 设为 null

    poseangularVelocity 设为 null

    poseemulatedPosition 设为 true

    否则:

    pose 设为 null

注意:XRPoseemulatedPosition 布尔值并不表示 baseSpace 的位置是否为模拟,只表示在计算 space 相对于 baseSpace 的位置时是否依赖于模拟。例如,具有 3DoF 跟踪的控制器,当其 targetRaySpacegripSpace 相对于 XRReferenceSpace 查询时,会报告 emulatedPositiontrue;但如果 targetRaySpace 的姿态在 gripSpace 下查询,则会报告 emulatedPositionfalse,因为这两个空间之间的关系应当是精确已知的。

6.2. XRReferenceSpace

XRReferenceSpace 是应用可用于与用户物理环境建立空间关系的几种常见 XRSpace 之一。

XRReferenceSpace 通常在整个 XRSession 期间保持静态,最常见的例外是用户在会话中途重新配置。每个 XRReferenceSpace原生原点 描述了一个坐标系,其中 +X 代表“右”,+Y 代表“上”,-Z 代表“前”。

enum XRReferenceSpaceType {
  "viewer",
  "local",
  "local-floor",
  "bounded-floor",
  "unbounded"
};

[SecureContext, Exposed=Window]
interface XRReferenceSpace : XRSpace {
  [NewObject] XRReferenceSpace getOffsetReferenceSpace(XRRigidTransform originOffset);

  attribute EventHandler onreset;
};

每个 XRReferenceSpace 有一个 type,其类型为 XRReferenceSpaceType

XRReferenceSpace 通常通过调用 requestReferenceSpace() 获得,如果传入的 XRReferenceSpaceType 枚举值受支持,则会创建一个 XRReferenceSpace 实例(或其扩展接口)。type 指示参考空间的跟踪行为:

注意:假设底层平台对参考空间 Y 轴的规范在不同 XRReferenceSpace 类型间保持一致。也就是说,若 XR 系统支持多种参考空间,则它们的 Y 轴应在该 XRSession 生命周期内保持平行且同向。但 "viewer" 不依赖底层平台朝向规范。"unbounded" 参考空间应在原点接近其他参考空间时对齐 Y 轴,用户远距离移动时可偏离。

支持 "local" 参考空间的设备必须支持 "local-floor" 参考空间(必要时可模拟),反之亦然。

onreset 属性是 事件处理 IDL 属性,用于 reset 事件类型。

当为 XRSession sessionXRReferenceSpaceType type 请求 XRReferenceSpace 时,用户代理必须 创建参考空间,步骤如下:

  1. 初始化 referenceSpace

    typebounded-floor

    referenceSpacesession 相关 realm 中的新 XRBoundedReferenceSpace

    否则:

    referenceSpacesession 相关 realm 中的新 XRReferenceSpace

  2. referenceSpacetype 设为 type

  3. referenceSpacesession 设为 session

  4. 返回 referenceSpace

检查给定参考空间类型 typeXRSession session 是否支持参考空间,步骤如下:
  1. type 不在 session已授予功能集 中,返回 false

  2. typeviewer,返回 true

  3. typelocallocal-floor,且 session沉浸式会话,返回 true

  4. typelocallocal-floor,且 XR 设备支持报告朝向数据,则返回 true

  5. typebounded-floorsession沉浸式会话,返回 XR 设备是否支持有界参考空间

  6. typeunboundedsession沉浸式会话,且 XR 设备 支持用户周围无限距离的稳定跟踪,则返回 true

  7. 返回 false

getOffsetReferenceSpace(originOffset) 方法被调用时必须按以下步骤执行:
  1. base 为该方法被调用的 XRReferenceSpace

  2. 初始化 offsetSpace

    baseXRBoundedReferenceSpace 的实例:

    offsetSpacebase 相关 realm 中的新 XRBoundedReferenceSpace,并将 offsetSpaceboundsGeometry 设为 baseboundsGeometry,每个点都乘以 originOffsetinverse

    否则:

    offsetSpacebase 相关 realm 中的新 XRReferenceSpace

  3. offsetSpacetype 设为 basetype

  4. offsetSpaceorigin offset 设为 baseorigin offsetoriginOffsetbase 相关 realm 下 相乘的结果。

  5. 返回 offsetSpace

注意:预期部分应用会用 getOffsetReferenceSpace() 实现基于鼠标、键盘、触摸或手柄输入的场景导航控制。这会导致 getOffsetReferenceSpace() 在活跃输入期间每帧调用一次。因此强烈建议 UA 让通过 getOffsetReferenceSpace() 创建新 XRReferenceSpace 的操作保持轻量。

6.3. XRBoundedReferenceSpace

XRBoundedReferenceSpace 扩展了 XRReferenceSpace,包含 boundsGeometry,用于指示用户空间的预设边界。

[SecureContext, Exposed=Window]
interface XRBoundedReferenceSpace : XRReferenceSpace {
  readonly attribute FrozenArray<DOMPointReadOnly> boundsGeometry;
};

XRBoundedReferenceSpace 的原点必须位于地面,Y 轴在地面处等于 0XZ 的位置及朝向根据底层平台的规范初始化,通常应靠近房间中心,面向合理的前方。

注意:其他 XR 平台有时将 bounded-floor 参考空间的跟踪类型称为“房间级(room scale)”跟踪。XRBoundedReferenceSpace 并不适用于多房间空间、不平地面或非常大的开放区域。需要处理这些场景的内容应使用 unbounded 参考空间。

每个 XRBoundedReferenceSpace 有一个 原生边界几何,描述 XRBoundedReferenceSpace 的边界,用户可在此区域内安全移动。该多边形边界用 DOMPointReadOnly 数组表示,数组中的点构成安全空间边缘的环路。每个点表示相对于 原生原点 的偏移量(单位:米)。点必须以从上往下看时顺时针顺序排列,朝向 Y 轴负方向。每个点的 y 值必须为 0w 必须为 1。边界可视为从地面起无限高,该形状可以是凸的也可以是凹的。

原生边界几何中的每个点必须限制在参考空间原生原点的合理距离内。

注意:建议 原生边界几何中的点限制在原点各方向 15 米以内。

原生边界几何中的每个点还必须量化到足以防止指纹识别。为保证用户安全,量化后的点值不得超出平台报告的边界。

注意:建议 原生边界几何中的点量化到最接近的 5cm。

boundsGeometry 属性是一个 DOMPointReadOnly 数组, 其中每个条目等于 XRBoundedReferenceSpace原生边界几何 经过 inverse 原点偏移的预乘处理后的结果。换句话说,它在 XRBoundedReferenceSpace 坐标系下, 相对于 有效原点 提供了相同的边界。

如果 原生边界几何临时不可用(如 XR 设备初始化中、长时间丢失跟踪、或在多个预设空间间移动时),boundsGeometry 必须返回空数组。

要检查是否支持有界参考空间,执行以下步骤:
  1. 如果 XR 设备无法报告边界,返回 false。

  2. 如果 XR 设备无法识别用户物理地面的高度,返回 false。

  3. 返回 true。

注意:如果边界或地面高度在请求参考空间时尚未解析,但 XR 设备支持它们,仍可返回有界参考空间。

注意:内容不应要求用户移动到 boundsGeometry 之外。若用户物理空间允许,也可能移动到边界之外,导致位置值超出多边形边界。这不是错误,应由内容优雅处理。

注意:内容通常不应自行可视化 boundsGeometry,因为向用户提供安全关键信息是用户代理的责任。

7. 视图(Views)

7.1. XRViewGeometry

包括 XRViewGeometry 接口混入体在内的对象,表示用于 XR 设备向用户呈现图像的显示,或用于收集现实世界视觉信息的传感器。这些对象包含一个视图几何

视图几何对应于用于在观察者参考空间坐标系承载对象屏幕空间之间转换的内外参集合。

视图几何有一个 承载对象,即该视图几何所包含数据的物理硬件。

视图几何承载对象有一个关联的 屏幕空间,即该承载对象读取数据或渲染数据的二维平面。

视图几何有一个关联的 视图偏移,即 XRRigidTransform,描述承载对象观察者参考空间坐标系中的位置和朝向。

注意:视图偏移没有约束,视图允许有不同朝向。这可出现在眼部显示屏居中有角度的头显设备中,也可能出现在更极端的 CAVE 渲染等场景。由于这个原因,像 z 排序和剔除等技术可能需要针对每只眼做。

视图几何有一个关联的 投影矩阵,是一个矩阵,描述渲染到承载对象时所用的投影。投影矩阵可以包含如剪切等变换,可能无法用简单视锥体精确描述。

注意:该矩阵的逆可用于“读取”屏幕空间像素并将其转换回以承载对象为原点的坐标系中。

[SecureContext, Exposed=Window] interface mixin XRViewGeometry {
  readonly attribute Float32Array projectionMatrix;
  [SameObject] readonly attribute XRRigidTransform transform;
};

每个 XRViewGeometry 有一个关联的 内部投影矩阵,存储其 投影矩阵。初始为 null

注意:transform 可用于许多渲染库中定位相机对象。如应用需要传统视图矩阵,可通过 transform.inverse.matrix 获得。

projectionMatrix 属性是底层 视图几何的投影矩阵强烈建议应用直接使用该矩阵,勿修改或分解。渲染时未使用此投影矩阵可能导致画面畸变或错位,给用户带来不适。该属性必须通过获取投影矩阵算法计算。

transform 属性是该对象的 XRRigidTransform,表示对象在用于获取该对象的 XRReferenceSpace 中的位置和朝向。

要为给定 XRViewGeometry view geometry 获取投影矩阵

  1. 如果 view geometry内部投影矩阵 不为 null,执行:

    1. 如对 内部投影矩阵 执行 IsDetachedBuffer 操作结果为 false,则返回 view geometry内部投影矩阵

  2. view geometry内部投影矩阵 设为 view geometry投影矩阵,在其相关 realm中新建 矩阵

  3. 返回 view geometry内部投影矩阵

7.2. XRView

XRView 描述了给定帧内 XR 场景的单个视图

视图对应于 XR 设备用于向用户呈现图像的显示或显示区域。它们用于获取与视图物理输出属性(包括视场、眼睛偏移和其他光学属性)高度对齐地渲染内容所需的全部信息。视图可以覆盖用户视野的重叠区域。不保证任何 XR 设备使用的视图数量或顺序有固定要求,也不要求在 XRSession 生命周期内 视图数量保持不变。

视图有一个关联的 eye,即 XREye,描述该视图应显示给哪只眼。如果视图没有固有的眼睛关联(如显示为单目),该值必须为 "none"

视图有一个 active 标志,在 XRSession 生命周期内可能会变化。主视图必须始终将 active 标志设为 true

注意:许多头戴显示器(HMD)会请求渲染两个视图,分别对应左右眼,而大多数魔窗设备只请求一个视图,但应用不应假设具体的视图配置。例如:魔窗设备如支持立体输出可能会请求两个视图,若关闭该模式出于性能原因可能又只请求一个。类似地,HMD 也可能为了宽视场或不同像素密度的显示请求超过两个视图。

视图有一个内部 viewport modifiable 标志,指示此时是否可以通过 requestViewportScale() 修改视口缩放。在动画帧开始时为 true,调用 getViewport() 时设为 false

视图有一个内部 requested viewport scale 值,表示该视图请求的视口缩放比例。初始为 1.0,可通过 requestViewportScale() 修改(若系统支持动态视口缩放)。

视图有一个内部 current viewport scale 值,表示该视图当前实际使用的视口缩放比例。初始为 1.0。当 getViewport() 成功应用视口变化时更新为 requested viewport scale

视图有一个 reference space,为用于在 getViewerPose() 获取此 视图XRReferenceSpace 空间。

注意:动态视口缩放允许应用以可变比例渲染到完整视口的子集,该比例每帧可变且无需重新分配。为确保渲染正确,XR 系统和应用必须一致同意当前视口。一个 XRView 可在一帧内多次调用 requestViewportScale(),但缩放比例要等到 getViewport() 被调用后才生效。动画帧内首次调用 getViewport 会立即应用视口变化并锁定缩放比例,成为后续帧的默认值。系统可通过 recommendedViewportScale 属性基于性能等建议缩放值。

enum XREye {
  "none",
  "left",
  "right"
};

[SecureContext, Exposed=Window] interface XRView {
  readonly attribute XREye eye;
  readonly attribute unsigned long index;
  readonly attribute double? recommendedViewportScale;

  undefined requestViewportScale(double? scale);
};

XRView includes XRViewGeometry;

transform 以其reference space为坐标系。

eye 属性描述底层视图的眼睛。该属性的主要作用是保证预渲染的立体内容能呈现正确的内容给正确的眼睛。

index 属性描述了当该 XRView 通过 getViewerPose() 返回的 views 数组中时的偏移量。

可选的 recommendedViewportScale 属性包含 UA 推荐的视口缩放值,应用可用其配置动态视口缩放。若系统未实现推荐缩放的启发式或方法,则为 null。不为 null 时,该值必须大于 0.0 且小于等于 1.0 的数值,并且必须量化以避免泄漏详细性能或 GPU 利用率数据。

注意:建议通过从一组有限的可能缩放值中四舍五入推荐缩放值,并在接近边界时使用滞后,避免缩放值快速变化导致视觉不适。

每个 XRView 有一个 session,即产生该视图的 XRSession

每个 XRView 有一个 frame,即产生该视图的 XRFrame

每个 XRView 有一个 underlying view,即其所代表的底层视图

requestViewportScale(scale) 方法请求用户代理将该视口的requested viewport scale 设为指定值。

当在 XRView xrview 上调用该方法时,用户代理必须执行以下步骤:

  1. 如果 scale 为 null 或 undefined,则中止这些步骤。

  2. 如果 scale 小于等于 0.0,则中止这些步骤。

  3. 如果 scale 大于 1.0,则将 scale 设为 1.0。

  4. viewxrviewunderlying view

  5. viewrequested viewport scale 设为 scale

注意:该方法忽略 null 或 undefined 缩放值,以便应用可安全地写 view.requestViewportScale(view.recommendedViewportScale),即使系统未提供推荐值。

视图列表中任一视图active标志发生变化时,可以为 XRSession session 更新视口,步骤如下:

  1. layerrenderStatebaseLayer

  2. 如果 layernull,中止这些步骤。

  3. layer视口对象列表 设为空列表

  4. 视图列表中每个active视图 view

    1. viewport获取缩放视口的结果,使用与 view 相关的 全尺寸视口列表session

    2. 追加 viewportlayer视口对象列表

要为指定的 XRView viewXRSession session 获取缩放视口,请执行以下步骤:

  1. glFullSizedViewport 为与 view 关联的 WebGL 视口,取自 全尺寸视口列表

  2. scaleview当前视口缩放

  3. 用户代理可以选择对 scale 进行限制,以施加最小视口缩放因子。

  4. glViewport 为新的 WebGL 视口

  5. glViewportwidth 设为小于等于 glFullSizedViewportwidth 乘以 scale 的整数值。

  6. 如果 glViewportwidth 小于 1,则设为 1。

  7. glViewportheight 设为小于等于 glFullSizedViewportheight 乘以 scale 的整数值。

  8. 如果 glViewportheight 小于 1,则设为 1。

  9. glViewportx 分量设为 glFullSizedViewportx 分量(含)到 glFullSizedViewportx 分量加 glFullSizedViewportwidthglViewportwidth(含)之间的整数值。

  10. glViewporty 分量设为 glFullSizedViewporty 分量(含)到 glFullSizedViewporty 分量加 glFullSizedViewportheightglViewportheight(含)之间的整数值。

  11. viewport XRViewport,位于 session 的相关 realm

  12. viewportx 初始化为 glViewportx 分量。

  13. viewporty 初始化为 glViewporty 分量。

  14. viewportwidth 初始化为 glViewportwidth

  15. viewportheight 初始化为 glViewportheight

  16. 返回 viewport

注意:具体整数值的计算特意留给 UA 自行决定。直接向下取整 width/height 并保留 xy 偏移的方法是有效的,但 UA 也可以在约束范围内选取稍作调整的值,例如为了效率将视口对齐到 2 的幂像素网格。缩放视口必须完全包含在全尺寸视口内,但可由 UA 自行决定放置在全尺寸视口内的任何位置。大小和位置计算必须具确定性,并在同一会话内对于相同输入返回一致结果。

7.3. 主视图和次视图

当渲染该视图对沉浸式体验来说是必要的时,该视图为主视图主视图在整个 XRSession 生命周期内必须为active

当内容可以选择不渲染该视图,但依然能提供可用的沉浸式体验时,则该视图为次视图。当内容选择不渲染这些视图时,用户代理可以通过重投影重建这些视图。除非启用了 "secondary-views" 功能,否则次视图不得active

主视图的例子包括:手持AR会话的主单目视图,头戴AR/VR会话的主立体双视图,或CAVE会话的所有墙面视图。

次视图的例子包括用于视频捕捉的第一人称观察者视图,或"quad views"(每只眼有两视图,分辨率/视场不同)。

尽管内容应假设可能存在任意数量的视图,但我们预计相当多的内容会对 views 数组做出不正确假设,导致面对多于两个视图时出现问题。

由于用户代理可以通过重投影等机制渲染这些次视图, 有必要区分那些准备自行处理这些次视图的内容, 与那些要么意识不到存在次视图、要么不希望处理次视图的内容。

为此,暴露次视图的用户代理必须支持 "secondary-views" 功能描述符作为提示。启用此功能的内容应:

启用 "secondary-views" 时,用户代理可在需要时,将设备支持的任意次视图暴露给 XRSession。 此时用户代理不得用重投影重建次视图,应依赖内容实际渲染结果。

注意:建议内容通过 optionalFeatures 启用 "secondary-views",以确保最大兼容性。

如果次视图底层帧率较低,XRSession 可以选择:

7.4. XRViewport

XRViewport 对象描述了图形表面上的一个视口(矩形区域)。

[SecureContext, Exposed=Window] interface XRViewport {
  readonly attribute long x;
  readonly attribute long y;
  readonly attribute long width;
  readonly attribute long height;
};

xy 属性定义了相对于表面原点的偏移,widthheight 属性定义了视口的矩形尺寸。

视口值的具体解释依赖于其关联的图形 API 规范:

以下代码遍历 XRViewerPose 的所有 XRView,为每个视图从 XRWebGLLayer 查询 XRViewport,并用其设置 WebGL 渲染用的WebGL 视口
xrSession.requestAnimationFrame((time, xrFrame) => {
  const viewer = xrFrame.getViewerPose(xrReferenceSpace);

  gl.bindFramebuffer(xrWebGLLayer.framebuffer);
  for (xrView of viewer.views) {
    let xrViewport = xrWebGLLayer.getViewport(xrView);
    gl.viewport(xrViewport.x, xrViewport.y, xrViewport.width, xrViewport.height);

    // WebGL draw calls will now be rendered into the appropriate viewport.
  }
});

8. 几何基本体

8.1. 矩阵

WebXR 以矩阵的形式提供各种变换。WebXR 与 WebGL 一致,矩阵为 4x4,采用 16 元素 Float32Array,列主序存储,左乘列向量。可直接传给 WebGL 的 uniformMatrix4fv,用于创建等价的 DOMMatrix,或用于第三方数学库。

WebXR Device API 返回的矩阵为 16 元素 Float32Array,排列如下:
[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15]

将该矩阵作为变换应用于如下 DOMPointReadOnly 列向量:

{x:X, y:Y, z:Z, w:1}

得:

a0 a4 a8  a12  *  X  =  a0 * X + a4 * Y +  a8 * Z + a12
a1 a5 a9  a13     Y     a1 * X + a5 * Y +  a9 * Z + a13
a2 a6 a10 a14     Z     a2 * X + a6 * Y + a10 * Z + a14
a3 a7 a11 a15     1     a3 * X + a7 * Y + a11 * Z + a15

8.2. 归一化

许多算法要求向量或四元数归一化,即缩放其分量使整体模长为 1.0

归一化一组分量,UA 必须执行:

  1. length 为各分量平方和的平方根。

  2. length0,抛出 InvalidStateError 并中止。

  3. 每个分量除以 length 并设置该分量。

8.3. XRRigidTransform

XRRigidTransform 表示一个由 positionorientation 描述的变换。 解析 XRRigidTransform 时, 总是先应用 orientation,再应用 position

XRRigidTransform 包含一个内部矩阵,为一个矩阵

[SecureContext, Exposed=Window]
interface XRRigidTransform {
  constructor(optional DOMPointInit position = {}, optional DOMPointInit orientation = {});
  [SameObject] readonly attribute DOMPointReadOnly position;
  [SameObject] readonly attribute DOMPointReadOnly orientation;
  readonly attribute Float32Array matrix;
  [SameObject] readonly attribute XRRigidTransform inverse;
};

XRRigidTransform(position, orientation) 构造函数调用时必须执行以下步骤:

  1. transform XRRigidTransform,位于当前 realm

  2. transformposition DOMPointReadOnly,位于当前 realm

  3. positionw 不等于 1.0,抛出 TypeError 并中止。

  4. positionorientation 中任一值为 NaN 或其他非有限数(如 infinity),抛出 TypeError 并中止。

  5. 设置 transformpositionxyzw 分别为 position 对应的字典成员。

  6. transformorientation DOMPointReadOnly,位于当前 realm

  7. 设置 transformorientationxyzw 分别为 orientation 对应的字典成员。

  8. transform内部矩阵null

  9. 归一化 xyzw 这4个分量(属于 transformorientation)。

  10. 返回 transform

position 属性是一个三维点(米),描述变换的平移分量。该 positionw 属性必须为 1.0

orientation 属性是描述旋转分量的四元数。orientation 必须归一化到长度 1.0

matrix 属性将 positionorientation 描述的变换作为矩阵返回。该属性必须通过获取矩阵算法计算。

注意:此矩阵左乘列向量时,会先用 orientation 对向量做三维旋转,再用 position 平移。数学上列向量记法是 M = T * R,T 是 position 对应的平移矩阵,R 是 orientation 对应的旋转矩阵。

要为 XRRigidTransform transform 获取矩阵

  1. 如果 transform内部矩阵 不为 null,则执行:

    1. IsDetachedBuffer 操作对 内部矩阵 结果为 false,则返回 transform内部矩阵

  2. translation 为新的矩阵,为列向量平移矩阵,对应 position。若 position(x, y, z),该矩阵为:

    Mathematical expression for column-vector translation matrix

  3. rotation 为新的矩阵,为列向量旋转矩阵,对应 orientation。若 orientation 为单位四元数 (qx, qy, qz, qw),矩阵为:

    Mathematical expression for column-vector rotation matrix

  4. transform内部矩阵 设为在 transform相关 realm中新建的 Float32Array,其值为 translation * rotation 的结果(左乘),即 translation * rotation。数学上,矩阵为:

    Mathematical expression for matrix of multiplying translation and rotation with translation on the left

  5. 返回 transform内部矩阵

inverse 属性返回 XRRigidTransform 在 transform 相关 realm 下的一个实例,若应用于被 transform 变换过的对象,则可将其还原到初始姿态。该属性应懒计算。由 inverse 返回的 XRRigidTransform 必须以 transform 作为其 inverse

XRRigidTransformposition{ x: 0, y: 0, z: 0, w: 1 }orientation{ x: 0, y: 0, z: 0, w: 1 },则称为单位变换

相乘两个 XRRigidTransform BA,位于 Realm realm,UA 必须执行:

  1. result XRRigidTransform,位于 realm

  2. resultmatrix 设为 realm中新建 Float32Array,值为 Bmatrix 左乘 Amatrix

  3. resultorientation 设为 realm中新建 DOMPointReadOnly,表示 result matrix 左上 3x3 子矩阵对应的旋转四元数。

  4. resultposition 设为 realm中新建 DOMPointReadOnly,取自 result matrix 的第四列向量。

  5. 返回 result

result 表示从 A 源空间到 B 目标空间的变换。

注意:这等价于构造一个 XRRigidTransform,其 orientation 为 A 与 B 的旋转的合成,position 为 A 的 position 经 B 的 orientation 旋转后,加上 B 的 position。

9. 姿态(Pose)

9.1. XRPose

XRPose 描述了相对于 XRSpace 的空间位置和朝向。

[SecureContext, Exposed=Window] interface XRPose {
  [SameObject] readonly attribute XRRigidTransform transform;
  [SameObject] readonly attribute DOMPointReadOnly? linearVelocity;
  [SameObject] readonly attribute DOMPointReadOnly? angularVelocity;

  readonly attribute boolean emulatedPosition;
};

transform 属性描述了相对于基准 XRSpace 的位置和朝向。

linearVelocity 属性以每秒米数描述相对于基准 XRSpace 的线速度。如果用户代理无法填充此值,可以返回 null

angularVelocity 属性以每秒弧度描述相对于基准 XRSpace 的角速度。如果用户代理无法填充此值,可以返回 null

emulatedPosition 属性当 transform 表示基于传感器读数主动跟踪的 6DoF 姿态时为 false;若其 position 包含如脖子或手臂模型等计算偏移时为 true估算地面高度在判断 XRPose 是否包含 计算偏移时不得考虑。

9.2. XRViewerPose

XRViewerPose 是一个 XRPose,描述 XR 场景中由 XR 设备 跟踪的观察者的状态。观察者 可以表示被跟踪的硬件、用户头部相对硬件的观测位置,或用于计算 XR 场景一系列视点的其它方式。XRViewerPose 只能相对于 XRReferenceSpace 查询。除 XRPose 的值外,还提供包含刚性变换以指示视点和投影矩阵的 视图 数组。应用在渲染 XR 场景帧时应使用这些值。

[SecureContext, Exposed=Window] interface XRViewerPose : XRPose {
  [SameObject] readonly attribute FrozenArray<XRView> views;
};

views 数组是一组 XRView,描述 XR 场景的各个视点,这些视点相对于查询 XRReferenceSpace 时使用的 XRViewerPose。数组中的每个 XR 场景 视图 都必须被渲染,才能在 XR 设备 上正确显示。每个 XRView 包含用于指示视点和投影矩阵的刚性变换,并可用于在需要时从图层查询 XRViewport

注意: XRViewerPosetransform 可用于为场景旁观者视角或多用户交互定位观察者的图形表示。

10. 输入

10.1. XRInputSource

XRInputSource 表示一个XR 输入源,即任何允许用户在与观察者处于同一虚拟空间内执行定向操作的输入机制。XR 输入源的示例包括但不限于手持控制器、光学跟踪的手、以及基于凝视的输入方法(这些方法依赖观察者的姿态)。未明确与XR 设备关联的输入机制(如传统手柄、鼠标、键盘)不应视为XR 输入源

enum XRHandedness {
  "none",
  "left",
  "right"
};

enum XRTargetRayMode {
  "gaze",
  "tracked-pointer",
  "screen",
  "transient-pointer"
};

[SecureContext, Exposed=Window]
interface XRInputSource {
  readonly attribute XRHandedness handedness;
  readonly attribute XRTargetRayMode targetRayMode;
  [SameObject] readonly attribute XRSpace targetRaySpace;
  [SameObject] readonly attribute XRSpace? gripSpace;
  [SameObject] readonly attribute FrozenArray<DOMString> profiles;
  readonly attribute boolean skipRendering;
};

注意:XRInputSource 接口也被 WebXR Gamepads Module 所扩展

handedness 属性描述XR 输入源(如果有的话)关联的是哪只手。没有自然手型(如头戴设备上的控件)或当前无法确定手型的输入源,该属性必须设为 "none"

targetRayMode 属性描述用于产生目标射线的方法,并指示应用如何(如有需要)向用户展示该射线。

targetRaySpace 属性是一个 XRSpace,其原生原点用于跟踪 XRInputSource 的首选指向射线的空间位置和朝向(沿其 -Z 轴),定义见 targetRayMode

对于 targetRayMode"transient-pointer" 的输入源,targetRaySpace 表示交互开始时指向目标的射线。其姿态应在该 XRInput 的 gripSpace 内保持静态。

gripSpace 属性是一个 XRSpace,其原生原点用于跟踪用于渲染虚拟物体以看似被用户手持的姿态。如果用户手持一根直棒,该 XRSpace 会将原点放在其弯曲手指的中心,-Z 轴沿棒指向拇指。X 轴垂直于手背,右手背朝 +X,左手背朝 -XY 轴由 XZ 关系推断,+Y 大致沿手臂方向。

skipRendering 属性表示该输入源是可见的,且当前会话可能无需渲染它。如果 skipRendering 为 true 且 targetRayMode 为 "tracked-pointer",用户代理必须确保始终向用户展示 XR 输入源 的表示。

控制器被展示给用户的情形包括:控制器位于显示器与用户之间、显示器透明、或控制器由操作系统渲染。

skipRendering 是给开发者的提示,表示无需渲染输入源(如控制器)。但应继续渲染拾取射线和光标。

对于 targetRayMode"transient-pointer" 的输入源,gripSpace 应为相关用户手势空间,如无则应为用户可控制的其它空间(如 ViewerSpace、其他 XRInput 的 gripSpace 或 targetRaySpace)。这样用户仍可操控 targetRaySpace。

gripSpace 必须为 null,如果输入源本身不可被跟踪,例如 targetRayMode"gaze""screen"

profiles 属性是一个列表,包含 输入配置文件名,指示输入源的优选视觉表现和行为。

输入配置文件名 是不含空格的 ASCII 小写 DOMString,各单词用连字符(-)连接。应优先用设备厂商推荐的描述性名称。如平台有适宜标识(如 USB 厂商与产品 ID)可使用,但不能用唯一标识单一设备的值(如序列号)。输入配置文件名不得包含手型信息。多个 UA 暴露同一设备时应尽量报告相同 输入配置文件名WebXR Input Profiles Registry 是推荐管理输入配置文件名的位置。

profiles 按特异性降序排列。列表中第一个之后的 输入配置文件名 应为设备的替代表现,如更通用/更早版本、足够类似的广为人知设备,或类型描述(如 "generic-trigger-touchpad")。如有多个 profile,布局必须互为超集/子集。

XRSessionmode"inline"profiles 必须为空列表。

UA 可选择仅报告适当通用的 输入配置文件名 或空列表。例如无法可靠识别输入设备、无匹配 profile、或 UA 希望隐藏输入设备时可用此方式。

例如,三星 HMD Odyssey 控制器是标准 Windows Mixed Reality 控制器的变种,且两者输入布局相同。因此 Odyssey 控制器的 profiles 可以为: ["samsung-odyssey", "microsoft-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]。 第一个 profile 最精确地描述外观,第二个为可接受的替代,最后一个为大致类型(带 trigger、squeeze、触摸板和摇杆的控制器)。
类似地,Valve Index 控制器兼容 HTC Vive 控制器,但 Index 多了按钮和轴,因此其 profiles 可为: ["valve-index", "htc-vive", "generic-trigger-squeeze-touchpad-thumbstick"]。此时 "valve-index" 布局为 "htc-vive" 的超集,且 appearance 更精确。最后一项仍为通用 fallback。
(字符串仅为示例,实际 profile 由 WebXR Input Profiles Registry 管理。)

注意: XRInputSourceXRSessioninputSources 数组中是“活的”,其值会原地更新。因此不能通过保存某帧属性引用并与后续帧对比来判断状态变化,开发者应复制属性内容进行帧间比较。

XR 输入源如果支持 主输入源,则它支持一个主操作主操作是平台相关的动作,触发时会产生 selectstartselectendselect 事件。主操作可能是按下扳机、触摸板、按钮、语音指令、手势等。如平台规范定义了推荐主输入,应采用,否则 UA 可自由选择。设备必须至少支持一个主输入源

XR 输入源若不支持主操作,则为跟踪输入源。这类输入主要用于提供姿态数据。 注:如用于腿部或道具的跟踪附件即为 跟踪输入源。如未做手势识别,跟踪手也视为 跟踪输入源

XR 输入源 sourceXRSession session 开始其 主操作时,UA 必须执行如下步骤:

  1. frame XRFrame,属于 session相关 realmsessionsessiontime 为动作发生时间。

  2. 排队一个任务触发输入源事件,事件名为 selectstart,frame 为 frame,source 为 source

XR 输入源 sourceXRSession session 结束其 主操作时,UA 必须执行如下步骤:

  1. frame XRFrame,属于 session相关 realmsessionsessiontime 为动作发生时间。

  2. 排队一个任务,执行如下:

    1. 触发输入源事件,事件名为 select,frame 为 frame,source 为 source

    2. 触发输入源事件,事件名为 selectend,frame 为 frame,source 为 source

每个 XR 输入源可定义一个主握持操作主握持操作是平台相关的动作,触发时会产生 squeezestartsqueezeendsqueeze 事件。主握持操作通常用于模拟抓握。例如按下握把触发器或做出抓握手势。如平台规范定义了推荐主握持操作应采用,否则 UA 可自行选择。

XR 输入源 sourceXRSession session 开始其 主握持操作时,UA 必须执行如下步骤:

  1. frame XRFrame,属于 session相关 realmsessionsessiontime 为动作发生时间。

  2. 排队一个任务触发输入源事件,事件名为 squeezestart,frame 为 frame,source 为 source

XR 输入源 sourceXRSession session 结束其 主握持操作时,UA 必须执行如下步骤:

  1. frame XRFrame,属于 session相关 realmsessionsessiontime 为动作发生时间。

  2. 排队一个任务,执行如下:

    1. 触发输入源事件,事件名为 squeeze,frame 为 frame,source 为 source

    2. 触发输入源事件,事件名为 squeezeend,frame 为 frame,source 为 source

有时平台特定行为会导致 主操作主握持操作 被中断或取消。例如,XR 输入源主操作主握持操作开始后但尚未结束时被从XR 设备移除。

XR 输入源 sourceXRSession session主操作被取消时,UA 必须执行如下步骤:

  1. frame XRFrame,属于 session相关 realmsessionsessiontime 为动作发生时间。

  2. 排队一个任务触发输入源事件,事件类型为 XRInputSourceEvent,事件名为 selectend,frame 为 frame,source 为 source

XR 输入源 sourceXRSession session主握持操作被取消时,UA 必须执行如下步骤:

  1. frame XRFrame,属于 session相关 realmsessionsessiontime 为动作发生时间。

  2. 排队一个任务触发输入源事件,事件类型为 XRInputSourceEvent,事件名为 squeezeend,frame 为 frame,source 为 source

10.2. 瞬时输入

某些XR 设备可能支持瞬时输入源,即XR 输入源仅在执行瞬时操作期间有意义,该操作可以是主操作(针对主输入源),也可以是跟踪输入源的设备专属辅助操作

一个例子是在"inline" XRSession中使用鼠标、触摸或手写笔输入,必须产生一个瞬时XRInputSource,其targetRayMode设为screen,作为主指针的主操作,以及非主指针的辅助操作

另一个例子是,操作系统发出的意图,其输入源于无法直接暴露的敏感信息(如基于凝视的交互)。这些会生成一个瞬时XRInputSource,其targetRayMode设为transient-pointer,作为主操作处理。

瞬时输入源仅在会话的活跃 XR 输入源列表中保留于瞬时操作期间。

瞬时输入源在处理瞬时操作时,遵循下列流程,而不是非瞬时主操作的算法:

瞬时输入源 source 针对 XRSession session 开始其 瞬时操作时,UA 必须运行以下步骤:

  1. frame新建 XRFrame,属于 session相关 realmsession 设为 session,时间为操作发生时。

  2. 排队一个任务,执行下列步骤:

    1. 如有必要,触发由 XR 输入源操作产生的 "pointerdown" 事件。

    2. 将 XR 输入源添加活动 XR 输入源列表

    3. 如果 瞬时操作主操作,则触发输入源事件,事件名为 selectstart, frame 为 frame,source 为 source

瞬时输入源 source 针对 XRSession session 结束其 瞬时操作时,UA 必须运行以下步骤:

  1. frame新建 XRFrame,属于 session相关 realmsession 设为 session,时间为操作发生时。

  2. 排队一个任务,执行下列步骤:

    1. 如果 瞬时操作主操作,则触发输入源事件,事件名为 select, frame 为 frame,source 为 source

    2. 如有必要,触发 XR 输入源操作产生的 "click" 事件。

    3. 如果 瞬时操作主操作,则触发输入源事件,事件名为 selectend, frame 为 frame,source 为 source

    4. 将 XR 输入源从 活动 XR 输入源列表移除。

    5. 如有必要,触发 XR 输入源操作产生的 "pointerup" 事件。

瞬时输入源 source 针对 XRSession session瞬时操作被取消时,UA 必须运行以下步骤:

  1. frame新建 XRFrame,属于 session相关 realmsession 设为 session,时间为操作发生时。

  2. 排队一个任务,执行下列步骤:

    1. 如果 瞬时操作主操作,则触发输入源事件,事件名为 selectend, frame 为 frame,source 为 source

    2. 将 XR 输入源从 活动 XR 输入源列表移除。

    3. 如有必要,触发 XR 输入源操作产生的 "pointerup" 事件。

10.3. XRInputSourceArray

XRInputSourceArray 表示一个列表,其中包含若干 XRInputSource。 它用于内容会随时间变动的列表场景(如 XRSessioninputSources 属性),优于frozen array type

[SecureContext, Exposed=Window]
interface XRInputSourceArray {
  iterable<XRInputSource>;
  readonly attribute unsigned long length;
  getter XRInputSource(unsigned long index);
};

length 属性表示 XRInputSourceArray 中包含的 XRInputSource 数量。

索引属性 getter 用于按下标获取 XRInputSource

11. 图层(Layers)

注意:本规范目前仅定义了 XRWebGLLayer 图层,未来的扩展预计会增加更多图层类型及其所用的图像源。

11.1. XRLayer

[SecureContext, Exposed=Window]
interface XRLayer : EventTarget {};

XRLayerXRWebGLLayer 以及未来扩展引入的其他图层类型的基类。

11.2. XRWebGLLayer

XRWebGLLayer 是一种图层,它提供一个 WebGL framebuffer 用于渲染,使 3D 图形能通过硬件加速渲染并在 XR 设备 上显示。

typedef (WebGLRenderingContext or
         WebGL2RenderingContext) XRWebGLRenderingContext;

dictionary XRWebGLLayerInit {
  boolean antialias = true;
  boolean depth = true;
  boolean stencil = false;
  boolean alpha = true;
  boolean ignoreDepthValues = false;
  double framebufferScaleFactor = 1.0;
};

[SecureContext, Exposed=Window]
interface XRWebGLLayer: XRLayer {
  constructor(XRSession session,
             XRWebGLRenderingContext context,
             optional XRWebGLLayerInit layerInit = {});
  // Attributes
  readonly attribute boolean antialias;
  readonly attribute boolean ignoreDepthValues;
  attribute float? fixedFoveation;

  [SameObject] readonly attribute WebGLFramebuffer? framebuffer;
  readonly attribute unsigned long framebufferWidth;
  readonly attribute unsigned long framebufferHeight;

  // Methods
  XRViewport? getViewport(XRView view);

  // Static Methods
  static double getNativeFramebufferScaleFactor(XRSession session);
};

每个 XRWebGLLayer 都有一个 context 对象,初始为 null,类型为 WebGLRenderingContextWebGL2RenderingContext 的实例。

每个 XRWebGLLayer 都有一个关联的 session,即创建它时用到的 XRSession

XRWebGLLayer(session, context, layerInit) 构造函数调用时必须执行以下步骤:

  1. layer XRWebGLLayer ,属于 session相关 realm

  2. 如果 sessionended 值为 true,抛出 InvalidStateError 并中止。

  3. 如果 context 已丢失,抛出 InvalidStateError 并中止。

  4. 如果 session沉浸式会话contextXR compatible 布尔值为 false,抛出 InvalidStateError 并中止。

  5. 初始化 layercontextcontext

  6. 初始化 layersessionsession

  7. 初始化 layerignoreDepthValues 如下:

    如果 layerInitignoreDepthValuesfalseXR Compositor 会利用深度值:

    初始化 layerignoreDepthValuesfalse

    否则:

    初始化 layerignoreDepthValuestrue

  8. 初始化 layercomposition enabled 布尔值如下:

    如果 sessioninline session

    初始化 layercomposition enabledfalse

    否则:

    初始化 layercomposition enabledtrue

  9. 如果 layercomposition enabledtrue
    1. 初始化 layerantialiaslayerInitantialias

    2. scaleFactorlayerInitframebufferScaleFactor

    3. UA 可根据需要对 scaleFactor 进行限制或取整,例如为了性能把缓冲区尺寸对齐到2的幂。

    4. framebufferSize推荐 WebGL framebuffer 分辨率,其宽高分别乘以 scaleFactor

    5. 初始化 layerframebuffer WebGLFramebuffer ,在 context相关 realm 下,是一个尺寸为 framebufferSize不透明 framebuffer,用 context 创建,session 初始化为 session,并用 layerInitdepthstencilalpha 参数。

    6. 分配并初始化与 sessionXR 设备 兼容的资源,包括 GPU 可访问内存缓冲区,以支持 layer 的合成。

    7. layer 的资源因任何原因无法创建,抛出 OperationError 并中止。

    否则:
    1. 初始化 layerantialiaslayercontext实际 context 参数 antialias

    2. 初始化 layerframebuffernull

  10. 返回 layer

注意:如果 XRWebGLLayercomposition enabled 布尔值被设为 false,则 XRWebGLLayerInit 对象上的所有值都会被忽略,因为 WebGLRenderingContext 的默认 framebuffer 已经使用 context 的 实际 context 参数 分配,无法被覆盖。

context 属性是创建 XRWebGLLayer 时所用的 WebGLRenderingContext

每个 XRWebGLLayer 有一个 composition enabled 布尔值,初始设为 true。如设为 false,表示该 XRWebGLLayer 不得分配自己的 WebGLFramebuffer, 并且其所有反映 framebuffer 属性的内容都必须反映 context 的默认 framebuffer 的属性。

framebuffer 属性是 XRWebGLLayer 的一个 WebGLFramebuffer 实例,如果 opaque framebuffer composition enabledtrue,否则为 nullframebuffer 的尺寸在 XRWebGLLayer 创建后不可被开发者调整。

不透明 framebuffer 与标准 WebGLFramebuffer 功能一致,但有如下特性,使其更类似于 默认 framebuffer

注意:用户代理必须遵循 truedepthstencil 值,这与 WebGL 创建绘图缓冲区时的行为一致。

附加到 不透明 framebuffer 的缓冲区在首次创建或每次处理 XR 动画帧 前必须清除为下表值。这与 WebGL context 默认 framebuffer 的行为相同。不透明 framebuffer 总会被清除,与关联的 WebGL context 的 preserveDrawingBuffer 设置无关。

缓冲区 清除值
颜色 (0, 0, 0, 0)
深度 1.0
模板 0

注意:只要能保证开发者无法访问其他进程的缓冲区内容,实现可以优化省略 不透明 framebuffer 所需的隐式清除操作。例如开发者显式清除时,隐式清除就不需要。

如果 XRWebGLLayer 创建时 alphatrue,则 framebuffer 必须由 RGBA 格式纹理支持。 如果 XRWebGLLayer 创建时 alphafalse,则 framebuffer 必须由 RGB 格式纹理支持。

XR Compositor 必须将 framebuffer 的像素视为 SRGB8_ALPHA8SRGB8 colorFormat 格式。

注意:这意味着 XR Compositor 在处理 framebuffer 纹理数据时,不能做线性 RGBARGB 到 gamma 的转换,否则最终像素会过亮,不符合普通 2D WebGLRenderingContext 的渲染效果。

XRWebGLLayer 被设为 沉浸式会话baseLayer 时,只要自上一个 XR 动画帧 以来发生过下述任一情况,不透明 framebuffer 的内容会在 XR 动画帧 完成后立即呈现到 沉浸式 XR 设备 上:

不透明 framebuffer 呈现给 沉浸式 XR 设备 之前,用户代理应确保所有渲染操作都已刷新到 不透明 framebuffer

每个 XRWebGLLayer 有一个 目标 framebuffer,若 composition enabledtrue,则为 framebuffer,否则为 context 的默认 framebuffer。

framebufferWidthframebufferHeight 属性分别返回 目标 framebuffer 附件的宽和高。

antialias 属性为 目标 framebuffer 是否支持 UA 选择的抗锯齿技术,true 表示支持,否则为 false

ignoreDepthValues 属性如为 true,表示 XR Compositor 渲染时不得使用深度缓冲区内容。如为 false,则表示深度缓冲内容将被 XR Compositor 利用,且应能代表渲染到该图层的场景。

深度缓冲存储的值应在 0.01.0 之间,其中 0.0 代表 depthNear 距离,1.0 代表 depthFar 距离,中间值线性插值。这也是 WebGL 默认行为。(参见 depthRange 函数 文档。)

注意:让场景的深度缓冲可用于合成器,可让部分平台实现更高质量和舒适度的增强(如改进重投影)。

fixedFoveation 属性控制 XR Compositor 使用的注视点渲染(foveation)量。如 UA 或设备不支持此属性,get 时应返回 null,set 为 no-op。设为 0 表示最小 foveation,1 表示最大。UA 可自行解释该值。如 fixedFoveation 级别变化,会在下一个 XRFrame 生效。

注意:固定注视点渲染(fixed foveation) 是一种在用户视野边缘以较低分辨率渲染内容的技术。可大幅提升受限于 GPU 填充性能的体验,降低功耗并提升眼部贴图分辨率。对低对比度背景图像等效果显著,对高对比度如文本效果有限。作者可每帧调整其级别以在性能与视觉质量间权衡。

每个 XRWebGLLayer 必须有一个 全尺寸视口列表,为 列表,包含 WebGL viewport,每个 视图一个,包括当前未 active 但可能变为 active次视图。视口必须 widthheight 均大于 0,且描述的矩形不得超出 目标 framebuffer 边界,且视口不能重叠。如 composition enabledfalse,该列表只含一个 WebGL viewport,覆盖 context 的整个默认 framebuffer。

每个 XRWebGLLayer 必须有一个 视口对象列表,为 列表,包含每个当前 active 视图XRViewport

getViewport() 用于查询给定 XRView 渲染到该图层时应使用的 XRViewport

getViewport(view) 方法在 XRWebGLLayer layer 上调用时,必须执行以下步骤:

  1. sessionviewsession

  2. framesessionanimation frame

  3. 如果 session 不等于 layersession,则抛出 InvalidStateError 并中止这些步骤。

  4. 如果 frameactive 布尔值为 false,则抛出 InvalidStateError 并中止这些步骤。

  5. 如果 viewframe 不等于 frame,则抛出 InvalidStateError 并中止这些步骤。

  6. 如果 viewport modifiable 标志为 trueviewrequested viewport scale 不等于 current viewport scale

    1. current viewport scale 设为 requested viewport scale

    2. 视口对象列表中,将与 view 关联的 XRViewport 设置为通过 获取缩放视口,从与 view 关联的 全尺寸视口列表session 得到的 XRViewport

  7. viewviewport modifiable 标志设为 false。

  8. viewport视口对象列表中与 view 关联的 XRViewport

  9. 返回 viewport

注意:viewport modifiable 标志即使 current viewport scale 没有更改也会被设为 false。这样保证 getViewport(view) 在同一动画帧内多次调用总是返回一致结果,第一次获取的值会被锁定用于该帧后续操作。如果应用在 getViewport() 之后调用 requestViewportScale(),则请求的值只会在未来帧再次调用 getViewport() 时生效。

每个 XRSession 必须确定一个原生 WebGL 帧缓冲分辨率,即与 XR 设备 物理像素分辨率匹配所需的 WebGL 帧缓冲像素分辨率。

要确定 XRSession session原生 WebGL 帧缓冲分辨率,需执行以下步骤:

  1. 如果 sessionmode 值不是 "inline", 则将 原生 WebGL 帧缓冲分辨率 设为,足以容纳所有 XRView 的帧缓冲像素,与显示屏下最大放大倍率区域的物理像素一一对应的分辨率,并中止这些步骤。如无方法可得,则可用 推荐 WebGL 帧缓冲分辨率

  2. 如果 sessionmode 值为 "inline", 则将 原生 WebGL 帧缓冲分辨率 设为 sessionrenderState输出画布的物理显示像素尺寸,在画布尺寸变化或 输出画布 变化时应重新评估本步骤。

此外,XRSession 还必须确定一个推荐 WebGL 帧缓冲分辨率,即能容纳所有 XRView,并在性能和质量间为一般应用提供较好平衡的 WebGL 帧缓冲像素分辨率。它可以小于、大于或等于原生 WebGL 帧缓冲分辨率。新建 不透明 framebuffer 时,将以该分辨率为基础,再分别按 XRWebGLLayerInitframebufferScaleFactor 缩放宽高。

注意:UA 可以采用任何方式估算 推荐 WebGL 帧缓冲分辨率。如有平台特定的推荐尺寸查询方法建议采用,但不是必须。framebufferScaleFactorgetNativeFramebufferScaleFactor() 的缩放因子分别应用于宽和高,因此缩放为2时总像素数变为4倍。如平台暴露的是按像素数的面积缩放,UA 需取平方根以换算为 WebXR 缩放因子。

getNativeFramebufferScaleFactor(session) 方法调用时,必须执行以下步骤:

  1. sessionthis

  2. 如果 sessionended 值为 true,则返回 0.0 并中止这些步骤。

  3. 返回 session推荐 WebGL 帧缓冲分辨率 的宽和高分别乘以的值,以得到 session原生 WebGL 帧缓冲分辨率

11.3. WebGL 上下文兼容性

为了让 WebGL 上下文可用作沉浸式 XR 图像的源,必须在该兼容图形适配器上创建,适配于沉浸式 XR 设备。什么是兼容图形适配器取决于平台,但通常指该适配器能够无明显延迟地向沉浸式 XR 设备提供图像。如果 WebGL 上下文不是在兼容图形适配器上创建,通常需要在该适配器上重新创建后,才能与XRWebGLLayer 一起使用。

注意:在只有一个 GPU 的 XR 平台上,可以安全地认为该 GPU 与平台公布的沉浸式 XR 设备兼容,因此任何硬件加速的 WebGL 上下文也兼容。在同时有集成显卡和独立显卡的 PC 上,独立显卡通常被认为是兼容图形适配器,因为它一般性能更强。在安装了多个图形适配器的台式机上,物理上连接有沉浸式 XR 设备的那块适配器很可能被视为兼容图形适配器

注意:"inline" 会话使用与 canvas 一样的图形适配器渲染,因此不需要 xrCompatible 上下文。

partial dictionary WebGLContextAttributes {
    boolean xrCompatible = false;
};

partial interface mixin WebGLRenderingContextBase {
    [NewObject] Promise<undefined> makeXRCompatible();
};

当用户代理实现本规范时,必须在每个 WebGLRenderingContextBase 上设置一个布尔值 XR compatible,初始为 false。一旦 XR compatible 布尔值设为 true,该上下文即可用于来自当前沉浸式 XR 设备的任何 XRSession 的图层。

注意:此标志会引起同步阻塞行为,不推荐使用。建议改用 makeXRCompatible() 实现异步。

XR compatible 布尔值既可以在上下文创建时设置,也可以在创建后设置(可能导致上下文丢失)。如需在创建时设置 XR compatible,则在请求 WebGL 上下文时,xrCompatible context 创建属性必须设为 true。如果请求文档的origin 不被允许使用 "xr-spatial-tracking" 权限策略,则 xrCompatible 无效。

xrCompatible 标志在 WebGLContextAttributes 上如为 true,则请求用户代理 兼容图形适配器 创建 WebGL 上下文,适配于 沉浸式 XR 设备。如果用户代理成功,则创建的上下文 XR compatible 布尔值为 true。要获取 沉浸式 XR 设备,应调用 ensure an immersive XR device is selected

注意:ensure an immersive XR device is selected 需要并行执行,这会导致主线程阻塞。UA 应在控制台给出警告,建议使用 makeXRCompatible()

以下代码创建一个与沉浸式 XR 设备兼容的 WebGL 上下文,并用它创建一个 XRWebGLLayer
function onXRSessionStarted(xrSession) {
  const glCanvas = document.createElement("canvas");
  const gl = glCanvas.getContext("webgl", { xrCompatible: true });

  loadWebGLResources();

  xrSession.updateRenderState({ baseLayer: new XRWebGLLayer(xrSession, gl) });
}

如需在上下文创建后再设置XR compatible布尔值,可用 makeXRCompatible() 方法。

注意:在某些系统上,此标志可能会启用高性能独显,或将所有命令代理至设备上的 GPU。如果是否使用 XR 不确定,建议只在即将启动沉浸式会话时调用 makeXRCompatible()

makeXRCompatible() 方法确保 WebGLRenderingContextBase 运行在兼容图形适配器上,适配于沉浸式 XR 设备

调用此方法时,UA 必须执行以下步骤:

  1. 如果请求文档的origin 不被允许使用 "xr-spatial-tracking" 权限策略resolve promise 并返回。XR 权限策略被禁用时,应表现为本地没有 XR 设备,因为 makeXRCompatible() 设计为一次性设置的方法。

  2. promise新建 Promise,属于该 WebGLRenderingContextBase 所在 Realm。

  3. contextthis

  4. 以下步骤并行执行:

    1. deviceensure an immersive XR device is selected 的结果。

    2. 设置 contextXR compatible 布尔值如下:

      如果 contextWebGL context lost flag 已设:

      排队一个任务,将 contextXR compatible 设为 false,并用 InvalidStateError 拒绝 promise

      如果 devicenull

      排队一个任务,将 contextXR compatible 设为 false,并用 InvalidStateError 拒绝 promise

      如果 contextXR compatibletrue

      排队一个任务resolve promise

      如果 context 是在 兼容图形适配器上为 device 创建的:

      排队一个任务,将 contextXR compatible 设为 trueresolve promise

      否则:

      排队一个任务WebGL 任务源 上,执行以下步骤:

      1. 强制 context 丢失。

      2. 按 WebGL 规范处理上下文丢失

        1. canvascontextcanvas

        2. 如果 contextwebgl context lost flag 已设,终止这些步骤。

        3. contextwebgl context lost flag

        4. 设每个由 context 创建的 WebGLObject 实例的invalidated 标志。

        5. 禁用所有扩展,除了 "WEBGL_lose_context"。

        6. 排队一个任务WebGL 任务源 上,执行以下步骤:

          1. 触发 WebGL context 事件 e,事件名 "webglcontextlost",目标 canvasstatusMessage 设为 ""。

          2. 如果 ecanceled flag 未设,则用 AbortError 拒绝 promise,并终止。

          3. 以下步骤并行执行。

            1. 等待在 兼容图形适配器上的可恢复绘图缓冲区,适配于 device

            2. 将一个任务排入队列WebGL task source 上执行以下步骤:

              1. 恢复 上下文兼容的 图形适配器 上,用于 device.

              2. contextXR compatible 设为 true

              3. resolve promise

  5. 返回 promise

此外,任何 WebGL context 丢失时,在触发 "webglcontextlost" 事件前应执行下面步骤:

  1. 将 context 的 XR compatible 设为 false

以下代码展示如何用已存在的 WebGL 上下文创建一个 XRWebGLLayer
const glCanvas = document.createElement("canvas");
const gl = glCanvas.getContext("webgl");

loadWebGLResources();

glCanvas.addEventListener("webglcontextlost", (event) => {
  // 指示 WebGL 上下文可以恢复
  event.canceled = true;
});

glCanvas.addEventListener("webglcontextrestored", (event) => {
  // 上下文丢失后 WebGL 资源需重新创建
  loadWebGLResources();
});

async function onXRSessionStarted(xrSession) {
  // 确保要用的 canvas 上下文与设备兼容,可能会导致丢失 context。
  await gl.makeXRCompatible();
  xrSession.updateRenderState({ baseLayer: new XRWebGLLayer(xrSession, gl) });
}

12. 事件

除非另有说明,本规范中所有排队任务任务源均为 XR 任务源

12.1. XRSessionEvent

XRSessionEvent 用于指示 XRSession 状态的变化时被触发。

[SecureContext, Exposed=Window]
interface XRSessionEvent : Event {
  constructor(DOMString type, XRSessionEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
};

dictionary XRSessionEventInit : EventInit {
  required XRSession session;
};

session 属性表示生成该事件的 XRSession

12.2. XRInputSourceEvent

XRInputSourceEvent 用于指示 XRInputSource 状态的变化时被触发。

[SecureContext, Exposed=Window]
interface XRInputSourceEvent : Event {
  constructor(DOMString type, XRInputSourceEventInit eventInitDict);
  [SameObject] readonly attribute XRFrame frame;
  [SameObject] readonly attribute XRInputSource inputSource;
};

dictionary XRInputSourceEventInit : EventInit {
  required XRFrame frame;
  required XRInputSource inputSource;
};

inputSource 属性表示生成该事件的 XRInputSource

frame 属性为一个 XRFrame,表示事件发生时的帧。它可能代表历史数据。对该 frame 调用 getViewerPose() 时必须抛出异常。

当用户代理需要以名称 nameXRFrame frameXRInputSource source 触发输入源事件时,必须执行以下步骤:

  1. XRInputSourceEventtype nameframe frameinputSource source 创建 event

  2. frameactive 布尔值设为 true

  3. frame 应用帧更新

  4. framesession分发 event

  5. frameactive 布尔值设为 false

12.3. XRInputSourcesChangeEvent

XRInputSourcesChangeEvent 用于指示 活动 XR 输入源列表 发生变化时被触发,该列表可用于 XRSession

[SecureContext, Exposed=Window]
interface XRInputSourcesChangeEvent : Event {
  constructor(DOMString type, XRInputSourcesChangeEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
  [SameObject] readonly attribute FrozenArray<XRInputSource> added;
  [SameObject] readonly attribute FrozenArray<XRInputSource> removed;
};

dictionary XRInputSourcesChangeEventInit : EventInit {
  required XRSession session;
  required sequence<XRInputSource> added;
  required sequence<XRInputSource> removed;

};

session 属性表示生成该事件的 XRSession

added 属性是一个 列表,包含在事件发生时被添加到 XRSessionXRInputSource

removed 属性是一个 列表,包含在事件发生时从 XRSession 移除的 XRInputSource

12.4. XRReferenceSpaceEvent

XRReferenceSpaceEvent 用于指示 XRReferenceSpace 状态的变化时被触发。

[SecureContext, Exposed=Window]
interface XRReferenceSpaceEvent : Event {
  constructor(DOMString type, XRReferenceSpaceEventInit eventInitDict);
  [SameObject] readonly attribute XRReferenceSpace referenceSpace;
  [SameObject] readonly attribute XRRigidTransform? transform;
};

dictionary XRReferenceSpaceEventInit : EventInit {
  required XRReferenceSpace referenceSpace;
  XRRigidTransform? transform = null;
};

referenceSpace 属性表示生成该事件的 XRReferenceSpace

可选的 transform 属性描述了 referenceSpace 的事件后位置和朝向(在事件前坐标系中)。如果 XRSystem 无法确定新旧坐标系的差异,则该属性可以为 null

注意:当头显在两个不同位置之间摘下又戴上时,referenceSpacereferenceSpace 可能会发生变化。在此类情况下,如果体验依赖于世界锁定内容,应提醒用户并重置场景。

12.5. XRVisibilityMaskChangeEvent

由于视锥体并不总是与矩形显示器精确相交,XRLayer 的整个区域可能不会被显示。该事件将通知体验哪些 XRView 区域被展示给用户。

当用户代理需要告知体验 XRLayer 的显示区域发生变化时,会触发 XRVisibilityMaskChangeEvent 事件。 体验可以选择只绘制该区域,这有助于提升性能。

注意:体验必须在 requestSession 的 promise 解决期间注册该事件,否则事件可能会触发且遮罩信息会丢失。

[SecureContext, Exposed=Window]
interface XRVisibilityMaskChangeEvent : Event {
  constructor(DOMString type, XRVisibilityMaskChangeEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
  readonly attribute XREye eye;
  readonly attribute unsigned long index;
  [SameObject] readonly attribute Float32Array vertices;
  [SameObject] readonly attribute Uint32Array indices;
};

dictionary XRVisibilityMaskChangeEventInit : EventInit {
  required XRSession session;
  required XREye eye;
  required unsigned long index;
  required Float32Array vertices;
  required Uint32Array indices;
};

session 属性表示生成该事件的 XRSession

eye 属性表示该遮罩所应用的 XREye

index 属性表示该遮罩应用到的 XRView视图列表 中的偏移量。

vertices 属性是一个 列表,包含 XY 坐标。体验必须假定 Z 坐标为 -1。每组 XYZ 坐标描述一个顶点。如果该数组为空,则应绘制 XRView 的整个区域。

indices 属性是一个 列表,用于描述 vertices 顶点列表中的索引。这些索引将描述该眼睛的 XRView 应绘制的区域。如果该数组为空,则应绘制 XRView 的整个区域。

该区域必须使用 projectionMatrixXRVieweye)和默认 XRRigidTransform 进行绘制。

注意:这意味着该区域不得使用当前 XRViewXRRigidTransformeye)进行绘制。

12.6. 事件类型

用户代理必须提供以下新事件。事件的注册和触发必须遵循 DOM 事件的常规行为。

除非文档的 origin 不被允许使用 "xr-spatial-tracking" 权限策略,用户代理必须在 XRSystem 对象上触发一个名为 devicechange 的事件,以指示沉浸式 XR 设备的可用性已发生变化。

每当 XRSession可见性状态发生变化时,用户代理必须在 XRSession 上,使用 XRSessionEvent 触发名为 visibilitychange 的事件

当会话结束时(无论是由应用程序还是用户代理),用户代理必须在 XRSession 上,使用 XRSessionEvent 触发名为 end 的事件

当会话的 激活 XR 输入源列表 发生变化时,用户代理必须在 XRSession 上,使用 XRInputSourcesChangeEvent 触发名为 inputsourceschange 的事件

当会话的 激活 XR 跟踪源列表 发生变化时,用户代理必须在 XRSession 上,使用 XRInputSourcesChangeEvent 触发名为 trackedsourceschange 的事件

当其某个 XRInputSource 开始其 主操作时,用户代理必须在 XRSession 上,使用 XRInputSourceEvent 触发名为 selectstart 的事件。事件类型必须为 XRInputSourceEvent。

当其某个 XRInputSource 结束其 主操作或一个已开始主操作的 XRInputSource 被断开时,用户代理必须在 XRSession 上,使用 XRInputSourceEvent 触发名为 selectend 的事件

当其某个 XRInputSource 完全完成一次 主操作时,用户代理必须在 XRSession 上,使用 XRInputSourceEvent 触发名为 select 的事件

当其某个 XRInputSource 开始其 主挤压操作时,用户代理必须在 XRSession 上,使用 XRInputSourceEvent 触发名为 squeezestart 的事件

当其某个 XRInputSource 结束其 主挤压操作或一个已开始主挤压操作的 XRInputSource 被断开时,用户代理必须在 XRSession 上,使用 XRInputSourceEvent 触发名为 squeezeend 的事件

当其某个 XRInputSource 完全完成一次 主挤压操作时,用户代理必须在 XRSession 上,使用 XRInputSourceEvent 触发名为 squeeze 的事件

XR 合成器 更改 XRSession内部名义帧率时,用户代理必须在 XRSession 上,使用 XRSessionEvent 触发名为 frameratechange 的事件

本地原点有效原点 发生不连续(即原点相对于用户环境的位置或朝向发生显著变化)时,(例如:用户重新校准 XR 设备后,或 XR 设备在丢失并恢复跟踪后自动调整原点)用户代理必须在 XRReferenceSpace 上,使用 XRReferenceSpaceEvent 触发名为 reset 的事件。当 XRBoundedReferenceSpaceboundsGeometry 发生变化时,也必须分发 reset 事件。如果 viewer 的姿态经历不连续,但 XRReferenceSpace 的原点物理映射保持稳定(如 viewer 在同一跟踪区域内暂时丢失并恢复跟踪时),则不得分发 reset 事件。当 unbounded 参考空间随时间对其 本地原点 进行微调以保持用户附近的空间稳定时,如果未发生显著不连续,也不得分发此事件。事件必须在利用新原点的任何 XR 动画帧 执行之前分发。必须在所有作为参考空间偏移空间的对象上分发 reset 事件,并且这些偏移 XRBoundedReferenceSpaceboundsGeometry 也应重新计算。

注意: 这意味着会话需要对那些有 reset 监听器的 XRReferenceSpace 保持强引用。

注意: 应用可以通过观察 emulatedPosition 布尔值来处理 viewer 位置的跳变。如果 viewer 位置的跳变与 emulatedPositiontrue 变为 false 同时发生,则表示 viewer 已恢复跟踪,其新位置反映了之前模拟值的修正。对于没有“瞬移”机制、用户只能物理移动的体验,这通常是应用期望的行为。但如果体验提供了“瞬移”机制,则在恢复跟踪后强行跳回 viewer 位置可能会令人不适。因此,这类应用在恢复跟踪时可以直接从 viewer 在虚拟世界的当前位置继续体验,将跳变吸收进瞬移偏移。为此,开发者可调用 getOffsetReferenceSpace() 创建一个新的参考空间,其 有效原点viewer 自前一帧以来的位置跳变进行调整。

13. 安全性、隐私性与舒适性注意事项

WebXR 设备 API 提供了强大的新特性,同时也带来了多种独特的隐私、安全和舒适性风险,用户代理必须采取措施加以缓解。

13.1. 敏感信息

在 XR 场景下,敏感信息包括但不限于用户可配置的数据(如瞳距 IPD)和基于传感器的数据(如 XRPose)。所有沉浸式会话都将暴露一定量的敏感数据,因为渲染内容需要用户的姿态信息。 但在某些情况下,相同的敏感信息也会通过 "inline" 会话暴露。

13.2. 用户意图

用户意图指的是用户对某个操作的主动行为和同意的信号。

在暴露敏感信息或允许对用户体验有重大影响的操作前,通常需要确保用户意图。这种意图可以通过多种方式传达或观察到。

注意:判断用户意图的常见方式是通过 UI 控件的瞬时激活,通常是“进入 VR”按钮。由于激活是瞬时的, 请求 XR 会话的浏览上下文必须是包含该 UI 控件的祖先同源域后代,并且最近是该浏览上下文的活动文档

13.2.1. 用户激活

瞬时激活在某些场景下可以作为用户意图的标志。

13.2.2. 启动 Web 应用

在某些环境下,页面可能以应用形式呈现,并明确用于运行沉浸式内容。在这种情况下,启动 Web 应用也可以作为用户意图的标志。

隐式同意指的是用户代理在未明确询问用户的情况下,根据 Web 应用的安装状态、访问频率和最近访问情况,或用户代理定义的用户明确表示想进入沉浸式体验的行为,来判断用户同意。鉴于 XR 数据的敏感性,强烈建议在依赖隐式信号时保持谨慎。

显式同意指的是用户代理在明确询问用户后,根据用户的答复来判断同意。当收集显式同意时,用户代理会说明请求内容,并为用户提供拒绝选项。请求用户同意的方式可以根据受保护特性和用户代理的选择以多种视觉形式呈现。Web 应用的安装状态可以作为显式同意的信号,前提是在安装时请求了某种显式同意

建议一旦为特定显式同意授予了某个,该同意应持续到浏览上下文结束。用户代理可根据用户意图的隐式或显式信号选择延长或缩短同意时长,但建议在偏离此建议时,尤其是依赖隐式信号时要谨慎。例如,专为运行沉浸式内容而安装的 Web 应用可持续保存用户同意,但如果沉浸式内容只是附加功能,则不应如此。

无论用户代理选择保存用户同意的时长如何,敏感信息只能由尚未结束XRSession暴露。

有多种非 XR API 会导致用户代理请求显式同意以使用某项功能。如果用户代理在存在活动沉浸式会话时需要请求用户同意,则必须在向用户显示同意请求前关闭会话。如果该功能的用户同意在活动沉浸式会话创建前已获得,则无需终止会话。

注意:此限制旨在确保所有用户代理在达成共识前行为一致,关于如何管理会话中途的显式同意。这不是长期要求。

13.4. 数据调整

在某些情况下,可以通过数据调整(如节流、量化、舍入、限制或以其他方式处理来自XR 设备的数据)来缓解安全和隐私威胁。有时即使已确认用户意图,也需要这样做以避免指纹识别。但数据调整缓解措施只能在不会导致用户不适的情况下使用。

13.4.1. 节流

节流指的是以低于原本可能的频率报告敏感信息。这种缓解措施有助于降低网站推断用户意图、位置或进行用户画像的能力。但如果使用不当,节流有很大风险导致用户不适。此外,在许多情况下,节流并不能完全缓解风险。

13.4.2. 舍入、量化与扰动

舍入、量化和扰动是三类修改原始数据的缓解措施。舍入通过减少用于表示数据的数字位数来降低精度。量化将连续数据限制为离散的值集合。扰动则是在数据中引入微小的随机误差。这些缓解措施有助于防止指纹识别,尤其是在不会对用户舒适性造成明显影响时特别有用。

13.4.3. 限制

限制指的是仅在数据处于特定范围内时才报告。例如,可以在用户远离批准位置超过一定距离时,舒适地限制位姿数据的报告。采用此缓解措施时应确保不会对用户体验产生负面影响。通常应避免在范围末端出现“硬停止”,以免造成突兀的用户体验。

13.5. 受保护的功能

API 暴露的敏感信息可分为若干类别,这些类别具有相似的威胁特征,并需要相应的保护措施。

13.5.1. 沉浸性

用户必须能够控制何时创建沉浸式会话,因为创建会话会对用户设备产生侵入性变化。例如,启动沉浸式会话会激活XR 设备的传感器,接管设备显示,并开始呈现沉浸式内容,这可能会终止其他应用对 XR 硬件的访问。在某些系统上,这还可能带来显著的电源或性能开销,或触发状态栏或商店的启动。

要判断给定global object是否允许沉浸式会话请求,用户代理必须执行以下步骤:

  1. 如果请求不是在global object具有瞬时激活启动 Web 应用时发起,则返回false

  2. 如果开始沉浸式会话用户意图尚不明确(无论是通过显式同意还是隐式同意),则返回false

  3. 返回true

启动"inline" 会话并不自动带有相同的要求,但根据会话的请求特性,可能会有额外要求。

要判断给定global object是否允许inline 会话请求,用户代理必须执行以下步骤:

  1. 如果会话请求包含任何必需特性可选特性,且请求不是在global object具有瞬时激活启动 Web 应用时发起,则返回false

  2. 如果global object不是Window,返回false

  3. 返回true

13.5.2. 位姿

当基于传感器数据时,XRPoseXRViewerPose 会暴露敏感信息,这些信息可能被用于输入嗅探、凝视追踪或指纹识别等多种方式。

要判断是否可以向XRSession session报告位姿,用户代理必须执行以下步骤:

  1. 如果session相关全局对象不是当前全局对象,返回false

  2. 如果sessionvisibilityState"hidden",返回false

  3. 判断是否可以返回位姿数据:

    如果用户代理已知位姿数据不会暴露可用于指纹识别的传感器数据

    返回true

    如果将对底层传感器数据应用数据调整以防止指纹识别或用户画像

    返回true

    如果用户意图已明确(通过显式同意隐式同意

    返回true

    否则

    返回false

注意:用户代理判断位姿数据不会暴露可指纹识别数据的方法由用户代理自行决定。

XRViewerPoseXRPose 的主要区别在于包含了 XRView 信息。当存在多个视图且这些视图之间的物理关系可由用户配置时,这些视图之间的关系被视为敏感信息,因为它可能被用于对用户进行指纹识别或画像。

如果 XRView 之间的关系可以唯一标识XR 设备,则用户代理必须对 XRView 数据进行匿名化处理,以防止指纹识别。匿名化的方法由用户代理自行决定。

注意:此外,如果 XRView 之间的关系受用户配置的瞳距(IPD)影响,则强烈建议用户代理在会话创建期间、报告任何 XRView 数据之前,要求明确同意

13.5.3. 参考空间

根据所用参考空间的不同,应用可能会暴露多种敏感信息

因此,各种参考空间类型的创建都受到限制,以确保暴露的敏感信息得到安全处理:

大多数参考空间要求使用该空间的用户意图已明确,无论是通过显式同意还是隐式同意。详情见特性要求表。

任何一组可相互关联的"local""local-floor""bounded-floor"参考空间,若能相互关联,则必须共享同一个原生原点;此限制仅在"unbounded"参考空间的创建受限时适用。

要判断两个空间spacebaseSpace之间位姿是否必须被限制,用户代理必须执行以下步骤:

  1. 如果spacebaseSpace中有任意一个是XRBoundedReferenceSpace,且另一个空间的原生原点超出了原生边界几何的合理距离,则返回 true。

  2. 如果spacebaseSpace中有任意一个是XRReferenceSpace,其type"local""local-floor",且两个空间的原生原点之间的距离大于用户代理确定的合理距离,则返回true

  3. 返回false

注意:对文档可见性的要求基于[DEVICE-ORIENTATION]

注意:建议相对于"local""local-floor"参考空间报告的位姿应限制在距XRReferenceSpace原生原点15米范围内。

注意:建议相对于XRBoundedReferenceSpace报告的位姿应限制XRBoundedReferenceSpace原生边界几何外1米范围内。

13.6. 可信环境

可信 UI是由用户代理呈现的界面,用户可以与之交互,但页面无法与之交互。用户代理必须支持显示可信 UI

可信 UI必须具备以下属性:

广义上,用户代理支持可信 UI有两种方式。一种是可信沉浸式 UI,即不退出沉浸模式的可信 UI。实现可信沉浸式 UI具有挑战性,因为XRWebGLLayer缓冲区会填满 XR 设备显示,用户代理通常不会“保留”像素用于自身。用户代理不要求支持可信沉浸式 UI,也可以临时暂停/退出沉浸模式,向用户显示非沉浸式可信 UI

注意:可信 UI的示例包括:

读取输入信息(头部姿态、输入姿态等)的能力对受信任UI的完整性构成风险,因为页面可能利用这些信息在用户与受信任UI交互时,窥探用户的选择,包括猜测键盘输入。为防止这种风险,当用户与受信任UI沉浸式或非沉浸式,如地址栏或系统对话框)交互时,用户代理必须将所有XRSession可见性状态设为"hidden""visible-blurred"。此外,为防止恶意页面监视其他页面的输入,当当前聚焦区域不属于创建该XRSession的文档时,用户代理必须将该XRSession可见性状态设为"hidden"

在为某个可信 UI实例选择"hidden""visible-blurred"时,用户代理必须考虑头部姿态信息是否构成安全风险。例如,涉及文本输入(尤其是密码输入)的可信 UI,用户在输入时头部姿态可能泄露输入内容。此时,用户代理还应停止暴露任何与眼动追踪相关的信息。

用户代理必须使用可信 UI显示权限提示。

如果虚拟环境无法以低延迟和高帧率持续跟踪用户头部运动,用户可能会感到迷失或身体不适。由于无法强制页面始终高效且正确地渲染内容,用户代理必须提供可跟踪的可信环境和异步于页面内容运行的XR Compositor。Compositor 负责合成可信与不可信内容。如果内容性能不佳、不提交帧或意外终止,用户代理应能继续呈现响应式的可信 UI

此外,页面内容还可能以非性能相关的方式让用户感到不适。例如,错误的跟踪、闪烁的颜色,以及有意冒犯、恐吓或惊吓用户的内容,都可能让用户希望快速退出 XR 体验。在这些情况下摘下 XR 设备可能并不总是快捷或可行。为此,用户代理必须为用户提供某种操作(如按下保留硬件按钮或执行手势),以便退出 WebXR 内容并显示用户代理的可信 UI

13.7. 上下文隔离

可信 UI 必须由独立的渲染上下文绘制,其状态与页面使用的任何渲染上下文(如 WebGL 渲染上下文)隔离。这可防止页面破坏可信 UI 上下文的状态,避免其无法正确渲染跟踪环境,也防止页面捕获可信 UI 的图像,导致隐私信息泄露。

此外,为防止 CORS 相关漏洞,每个浏览上下文都会看到 API 返回对象的新实例,如XRSession。如context等属性设置在某个XRWebGLLayer上时,只有同一相关 realm才能读取,不能被其他相关 realm读取,除非它们具有同源。同样,API 方法不得导致其他浏览上下文的可观察状态变化。例如,不会暴露可用于系统级方向重置的方法,否则恶意页面可反复调用,导致其他页面无法正常跟踪。但用户代理必须响应用户手势或系统菜单触发的系统级方向重置。

注意:这不适用于由某个浏览上下文进入沉浸模式、获取设备锁并可能在其他浏览上下文上触发devicechange事件导致的状态变化。

13.8. 指纹识别

由于 API 描述了用户可用的硬件及其能力,必然会为指纹识别提供额外的攻击面。虽然无法完全避免,但用户代理应采取措施缓解该问题。本规范限制每次只报告一个可用设备,防止利用罕见的多头显连接作为指纹信号。同时,被报告的设备没有字符串标识符,在创建 XRSession 前几乎不暴露设备能力信息,而创建 XRSession 时会暴露敏感信息,因此需要额外保护。

13.8.1. isSessionSupported() 的指纹识别注意事项

由于isSessionSupported()可在无用户激活的情况下调用,因此可能被用作指纹识别向量。

"xr-session-supported" 强特性限制对isSessionSupported() API 的访问。

"xr-session-supported"的权限相关算法和类型定义如下:

权限描述符类型
dictionary XRSessionSupportedPermissionDescriptor: PermissionDescriptor {
  XRSessionMode mode;
};

name 对于XRPermissionDescriptor"xr-session-supported"

13.8.2. 自动授予 "xr-session-supported" 的注意事项

Web 上的隐私与个性化常常存在张力。本节为如何权衡两者提供指导,并说明在何种情况下用户代理可以通过 isSessionSupported() 向站点描述浏览器的 WebXR 能力而不会降低隐私。

对于某些系统,"xr-session-supported" 可以根据下述标准自动授予。这有助于提升用户体验并缓解权限疲劳。

一组用户代理被 在用户代理字符串上无法区分,如果它们都 报告相同的 userAgentappVersion。 此类分组通常由运行的浏览器版本和平台/设备识别,但不能通过任何已连接外部设备的状态来区分。我们可以使用用户代理 中 在用户代理字符串上无法区分 的概念来 适当地评估指纹识别风险。

有些无法通过 user agent 字符串区分的用户代理将永不支持某种XRSessionMode会话。 例如:运行在某型号手机上的用户代理,该型号手机已知不支持移动 AR。在这些情况下,isSessionSupported() 始终报告不支持该XRSessionMode不会带来指纹识别风险,因为所有此类设备都会一致报告相同的值,且设备类型和型号可通过userAgent等方式推断。因此,在这些系统上,用户代理应自动拒绝相关XRSessionMode"xr-session-supported"

另一些无法通过 user agent 字符串区分的用户代理通常支持某种XRSessionMode会话。 例如:仅在 VR 头显内运行且已知支持 WebXR 的用户代理,除非被用户明确阻止,否则很可能支持"immersive-vr"会话。在这些情况下,报告XRSessionMode不支持虽然准确,但会暴露更多可唯一标识用户的信息。因此,始终报告该XRSessionMode可用,并允许requestSession()失败,更能保护隐私且通常不会让用户困惑。在这些系统上,用户代理应自动授予相关XRSessionMode"xr-session-supported"

对于 XR 能力高度可变的无法通过 user agent 字符串区分的用户代理(如支持 XR 外设的桌面系统),指纹识别风险最高。此类设备上的用户代理不应自动授予"xr-session-supported",以免isSessionSupported() API 提供额外的指纹识别信息。

注意:以下是处理此类情况的一些可接受做法:

无论采用哪种技术,都不应在未获得显式同意的情况下,泄露更多关于已连接 XR 硬件的信息。

14. 集成

14.1. 权限策略

本规范定义了一个策略控制特性,用于控制是否允许任何需要空间跟踪的XRSession通过requestSession()返回,以及是否允许通过isSessionSupported()devicechange事件指示支持需要空间跟踪的会话模式,在navigator.xr对象上。

该特性的标识符为"xr-spatial-tracking"

该特性的默认允许列表["self"]

注意:如果文档的未被允许使用"xr-spatial-tracking" 权限策略,则所有沉浸式会话都将被阻止,因为所有沉浸式会话都需要空间跟踪。Inline 会话仍然允许,但仅限于使用"viewer" XRReferenceSpace

14.2. 权限 API 集成

[permissions] API 为网站请求用户权限和查询已授予权限提供了统一方式。

"xr" 强特性的权限相关算法和类型定义如下:

权限描述符类型
dictionary XRPermissionDescriptor: PermissionDescriptor {
  XRSessionMode mode;
  sequence<DOMString> requiredFeatures;
  sequence<DOMString> optionalFeatures;
};

name 对于XRPermissionDescriptor"xr"

权限结果类型
[Exposed=Window]
interface XRPermissionStatus: PermissionStatus {
  attribute FrozenArray<DOMString> granted;
};
权限查询算法
查询 "xr" 权限时,使用XRPermissionDescriptor descriptorXRPermissionStatus status,用户代理必须执行以下步骤:
  1. statusstate 设为 descriptor权限状态

  2. 如果 statusstate"denied", 将 statusgranted 设为空 FrozenArray,并中止这些步骤。

  3. result解析请求特性的结果,参数为 descriptorrequiredFeaturesoptionalFeaturesmode

  4. 如果 resultnull,执行以下步骤:

    1. statusgranted 设为空 FrozenArray

    2. statusstate 设为 "denied"

    3. 中止这些步骤。

  5. 令 (consentRequired, consentOptional, granted) 为 result 的字段。

  6. statusgranted 设为 granted

  7. 如果 consentRequired 为空consentOptional 为空,则将 statusstate 设为 "granted",并中止这些步骤

  8. statusstate 设为 "prompt"

权限请求算法
使用XRPermissionDescriptor descriptorXRPermissionStatus status 请求 xr 权限时,用户代理必须执行以下步骤:
  1. statusgranted 设为空 FrozenArray

  2. requiredFeaturesdescriptorrequiredFeatures

  3. optionalFeaturesdescriptoroptionalFeatures

  4. device获取当前设备的结果,参数为 moderequiredFeaturesoptionalFeatures

  5. result解析请求特性的结果,参数为 requiredFeaturesoptionalFeaturesmode

  6. 如果 resultnull,执行以下步骤:

    1. statusstate 设为 "denied"

    2. 中止这些步骤。

  7. 令 (consentRequired, consentOptional, granted) 为 result 的字段。

  8. 此时用户代理可以询问用户是否允许调用算法使用 consentRequiredconsentOptional 中的任意特性。提示结果应在判断是否有明确用户意图时纳入考虑。

  9. consentRequired 中的每个 feature 执行以下步骤:

    1. 此时用户代理可以询问用户是否允许调用算法使用 feature。提示结果应在判断是否有明确用户意图时纳入考虑。

    2. 如果未确定有明确用户意图启用 feature,则将 statusstate 设为 "denied" 并中止这些步骤。

    3. 如果 feature 不在 granted 中,则将 feature 添加到 granted

  10. consentOptional 中的每个 feature 执行以下步骤:

    1. 此时用户代理可以询问用户是否允许调用算法使用 feature。提示结果应在判断是否有明确用户意图时纳入考虑。

    2. 如果未确定有明确用户意图启用 feature,则继续下一个条目。

    3. 如果 feature 不在 granted 中,则将 feature 添加到 granted

  11. statusgranted 设为 granted

  12. granted 的所有元素添加到 device已授予特性集合中,针对 mode

  13. statusstate 设为 "granted"

注意:用户代理可以在判断用户意图时,将所有请求特性的权限提示合并批量展示,也可以逐一展示。

注意:判断 Web 应用用户意图时,用户代理必须检查应用是否被用户明确以 Web 应用方式启动,而不能仅检查是否与已安装 Web 应用一致。

解析请求特性,给定 requiredFeaturesoptionalFeatures 以及 XRSessionMode mode,用户代理必须执行以下步骤:

  1. consentRequired 为一个空的 列表,元素类型为 DOMString

  2. consentOptional 为一个空的 列表,元素类型为 DOMString

  3. granted 为一个空的 列表,元素类型为 DOMString

  4. device获取当前设备(参数为 moderequiredFeaturesoptionalFeatures)的结果。

  5. previouslyEnableddevice 针对 mode已授予功能集合

  6. 如果 devicenulldevice支持模式列表包含 mode,执行以下步骤:

    1. 返回 元组 (consentRequired, consentOptional, granted)

  7. 默认功能表中与 mode 相关的每个 功能描述符 添加到 granted,如果尚未存在。

  8. 对于 requiredFeatures 中的每个 feature,执行以下步骤:

    1. 如果 featurenull继续下一个条目。

    2. 如果 feature 不是有效的 功能描述符,返回 null

    3. 如果 feature 已在 granted 中,继续下一个条目。

    4. 如果请求文档的 origin 不被允许使用 权限策略(由 功能需求表指示),返回 null

    5. 如果 sessionXR 设备支持 feature 所描述的功能,或用户代理以其他方式拒绝该功能,返回 null

    6. 如果 feature 所描述的功能需要 明确同意,且 feature 不在 previouslyEnabled 中,则将其添加到 consentRequired

    7. 否则,将 feature 添加到 granted

  9. 对于 optionalFeatures 中的每个 feature,执行以下步骤:

    1. 如果 featurenull继续下一个条目。

    2. 如果 feature 不是有效的 功能描述符继续下一个条目。

    3. 如果 feature 已在 granted 中,继续下一个条目。

    4. 如果请求文档的 origin 不被允许使用 权限策略(由 功能需求表指示),继续下一个条目。

    5. 如果 sessionXR 设备支持 feature 所描述的功能,或用户代理以其他方式拒绝该功能,继续下一个条目。

    6. 如果 feature 所描述的功能需要 明确同意,且 feature 不在 previouslyEnabled 中,则将其添加到 consentOptional

    7. 否则,将 feature 添加到 granted

  10. 返回 元组 (|consentRequired|, |consentOptional|, |granted|)

变更记录

2022 年 3 月 31 日候选推荐快照 的变更

2020 年 7 月 24 日工作草案 的变更

2019 年 10 月 10 日工作草案 的变更

新增特性:

变更:

2019 年 2 月 5 日首次公开工作草案 的变更

新增特性:

移除的特性:

变更:

15. 致谢

感谢以下个人对 WebXR 设备 API 规范的贡献:

特别感谢 Vladimir VukicevicUnity)为这一切的开启做出的贡献!

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合表达。 本文档规范性部分中的关键词 “必须(MUST)”、“不得(MUST NOT)”、“要求(REQUIRED)”、“应(SHALL)”、“不应(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“可以(MAY)” 和 “可选(OPTIONAL)” 应按照 RFC 2119 中的说明进行解释。 但为便于阅读,这些词在本规范中不会全部大写。

本规范的所有文本均为规范性内容,除非明确标记为非规范性、示例或注释。[RFC2119]

本规范中的示例以“例如”开头,或通过 class="example" 与规范性文本区分开来,如下所示:

这是一个信息性示例。

信息性注释以“注意(Note)”开头,并通过 class="note" 与规范性文本区分开来,如下所示:

注意,这是一个信息性注释。

一致性算法

以命令式表达的算法要求(如“去除所有前导空格字符”或“返回 false 并中止这些步骤”)应结合引入算法时所用关键词(如“必须”、“应该”、“可以”等)来解释其含义。

以算法或具体步骤表述的一致性要求,可以以任何方式实现,只要最终结果等价即可。 本规范中定义的算法旨在易于理解,并非为高性能而设计。鼓励实现者进行优化。

索引

本规范定义的术语

引用中定义的术语

参考文献

规范性引用

[DOM]
Anne van Kesteren。DOM 标准。实时标准。 网址:https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript 语言规范。 网址:https://tc39.es/ecma262/multipage/
[GEOMETRY-1]
Simon Pieters;Chris Harrelson。Geometry 接口模块 Level 1。2018年12月4日。CR。网址:https://www.w3.org/TR/geometry-1/
[HR-TIME-3]
Yoav Weiss。高精度时间。2024年11月7日。WD。网址:https://www.w3.org/TR/hr-time-3/
[HTML]
Anne van Kesteren 等。HTML 标准。实时标准。 网址:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren;Domenic Denicola。Infra 标准。实时标准。网址:https://infra.spec.whatwg.org/
[PERMISSIONS]
Marcos Caceres;Mike Taylor。Permissions。2025年9月26日。WD。网址:https://www.w3.org/TR/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland。Permissions Policy。2025年8月6日。WD。网址:https://www.w3.org/TR/permissions-policy-1/
[POINTEREVENTS]
Jacob Rossi;Matt Brubeck。Pointer Events。2019年4月4日。REC。网址:https://www.w3.org/TR/pointerevents/
[POINTERLOCK]
Vincent Scheib。Pointer Lock。2016年10月27日。REC。网址:https://www.w3.org/TR/pointerlock/
[REQUESTIDLECALLBACK]
Scott Haseley。requestIdleCallback()。2025年5月21日。WD。网址:https://www.w3.org/TR/requestidlecallback/
[RFC2119]
S. Bradner。在RFC中表示需求级别关键词的用法。1997年3月。最佳现行规范。网址:https://datatracker.ietf.org/doc/html/rfc2119
[WEBGL-2]
Dean Jackson;Jeff Gilbert。WebGL 2.0 规范。2017年8月12日。网址:https://www.khronos.org/registry/webgl/specs/latest/2.0/
[WEBIDL]
Edgar Chen;Timothy Gu。Web IDL 标准。实时标准。网址:https://webidl.spec.whatwg.org/
[WEBXRLAYERS-1]
Rik Cabanier。WebXR Layers API Level 1。2025年9月12日。WD。网址:https://www.w3.org/TR/webxrlayers-1/

资料性引用

[DEVICE-ORIENTATION]
Reilly Grant;Marcos Caceres。设备方向与运动。2025年2月12日。CRD。网址:https://www.w3.org/TR/orientation-event/
[WEBXR-AR-MODULE-1]
Brandon Jones;Manish Goregaokar;Rik Cabanier。WebXR 增强现实模块 - 第1级。2025年4月25日。CRD。网址:https://www.w3.org/TR/webxr-ar-module-1/

IDL 索引

partial interface Navigator {
  [SecureContext, SameObject] readonly attribute XRSystem xr;
};

[SecureContext, Exposed=Window] interface XRSystem : EventTarget {
  // Methods
  Promise<boolean> isSessionSupported(XRSessionMode mode);
  [NewObject] Promise<XRSession> requestSession(XRSessionMode mode, optional XRSessionInit options = {});

  // Events
  attribute EventHandler ondevicechange;
};

enum XRSessionMode {
  "inline",
  "immersive-vr",
  "immersive-ar"
};

dictionary XRSessionInit {
  sequence<DOMString> requiredFeatures;
  sequence<DOMString> optionalFeatures;
};

enum XRVisibilityState {
  "visible",
  "visible-blurred",
  "hidden",
};

[SecureContext, Exposed=Window] interface XRSession : EventTarget {
  // Attributes
  readonly attribute XRVisibilityState visibilityState;
  readonly attribute float? frameRate;
  readonly attribute Float32Array? supportedFrameRates;
  [SameObject] readonly attribute XRRenderState renderState;
  [SameObject] readonly attribute XRInputSourceArray inputSources;
  [SameObject] readonly attribute XRInputSourceArray trackedSources;
  readonly attribute FrozenArray<DOMString> enabledFeatures;
  readonly attribute boolean isSystemKeyboardSupported;

  // Methods
  undefined updateRenderState(optional XRRenderStateInit state = {});
  Promise<undefined> updateTargetFrameRate(float rate);
  [NewObject] Promise<XRReferenceSpace> requestReferenceSpace(XRReferenceSpaceType type);

  unsigned long requestAnimationFrame(XRFrameRequestCallback callback);
  undefined cancelAnimationFrame(unsigned long handle);

  Promise<undefined> end();

  // Events
  attribute EventHandler onend;
  attribute EventHandler oninputsourceschange;
  attribute EventHandler onselect;
  attribute EventHandler onselectstart;
  attribute EventHandler onselectend;
  attribute EventHandler onsqueeze;
  attribute EventHandler onsqueezestart;
  attribute EventHandler onsqueezeend;
  attribute EventHandler onvisibilitychange;
  attribute EventHandler onframeratechange;
};

dictionary XRRenderStateInit {
  double depthNear;
  double depthFar;
  boolean passthroughFullyObscured;
  double inlineVerticalFieldOfView;
  XRWebGLLayer? baseLayer;
  sequence<XRLayer>? layers;
};

[SecureContext, Exposed=Window] interface XRRenderState {
  readonly attribute double depthNear;
  readonly attribute double depthFar;
  readonly attribute boolean? passthroughFullyObscured;
  readonly attribute double? inlineVerticalFieldOfView;
  readonly attribute XRWebGLLayer? baseLayer;
};

callback XRFrameRequestCallback = undefined (DOMHighResTimeStamp time, XRFrame frame);

[SecureContext, Exposed=Window] interface XRFrame {
  [SameObject] readonly attribute XRSession session;
  readonly attribute DOMHighResTimeStamp predictedDisplayTime;

  XRViewerPose? getViewerPose(XRReferenceSpace referenceSpace);
  XRPose? getPose(XRSpace space, XRSpace baseSpace);
};

[SecureContext, Exposed=Window] interface XRSpace : EventTarget {

};

enum XRReferenceSpaceType {
  "viewer",
  "local",
  "local-floor",
  "bounded-floor",
  "unbounded"
};

[SecureContext, Exposed=Window]
interface XRReferenceSpace : XRSpace {
  [NewObject] XRReferenceSpace getOffsetReferenceSpace(XRRigidTransform originOffset);

  attribute EventHandler onreset;
};

[SecureContext, Exposed=Window]
interface XRBoundedReferenceSpace : XRReferenceSpace {
  readonly attribute FrozenArray<DOMPointReadOnly> boundsGeometry;
};

[SecureContext, Exposed=Window] interface mixin XRViewGeometry {
  readonly attribute Float32Array projectionMatrix;
  [SameObject] readonly attribute XRRigidTransform transform;
};

enum XREye {
  "none",
  "left",
  "right"
};

[SecureContext, Exposed=Window] interface XRView {
  readonly attribute XREye eye;
  readonly attribute unsigned long index;
  readonly attribute double? recommendedViewportScale;

  undefined requestViewportScale(double? scale);
};

XRView includes XRViewGeometry;

[SecureContext, Exposed=Window] interface XRViewport {
  readonly attribute long x;
  readonly attribute long y;
  readonly attribute long width;
  readonly attribute long height;
};

[SecureContext, Exposed=Window]
interface XRRigidTransform {
  constructor(optional DOMPointInit position = {}, optional DOMPointInit orientation = {});
  [SameObject] readonly attribute DOMPointReadOnly position;
  [SameObject] readonly attribute DOMPointReadOnly orientation;
  readonly attribute Float32Array matrix;
  [SameObject] readonly attribute XRRigidTransform inverse;
};

[SecureContext, Exposed=Window] interface XRPose {
  [SameObject] readonly attribute XRRigidTransform transform;
  [SameObject] readonly attribute DOMPointReadOnly? linearVelocity;
  [SameObject] readonly attribute DOMPointReadOnly? angularVelocity;

  readonly attribute boolean emulatedPosition;
};

[SecureContext, Exposed=Window] interface XRViewerPose : XRPose {
  [SameObject] readonly attribute FrozenArray<XRView> views;
};

enum XRHandedness {
  "none",
  "left",
  "right"
};

enum XRTargetRayMode {
  "gaze",
  "tracked-pointer",
  "screen",
  "transient-pointer"
};

[SecureContext, Exposed=Window]
interface XRInputSource {
  readonly attribute XRHandedness handedness;
  readonly attribute XRTargetRayMode targetRayMode;
  [SameObject] readonly attribute XRSpace targetRaySpace;
  [SameObject] readonly attribute XRSpace? gripSpace;
  [SameObject] readonly attribute FrozenArray<DOMString> profiles;
  readonly attribute boolean skipRendering;
};

[SecureContext, Exposed=Window]
interface XRInputSourceArray {
  iterable<XRInputSource>;
  readonly attribute unsigned long length;
  getter XRInputSource(unsigned long index);
};

[SecureContext, Exposed=Window]
interface XRLayer : EventTarget {};


typedef (WebGLRenderingContext or
         WebGL2RenderingContext) XRWebGLRenderingContext;

dictionary XRWebGLLayerInit {
  boolean antialias = true;
  boolean depth = true;
  boolean stencil = false;
  boolean alpha = true;
  boolean ignoreDepthValues = false;
  double framebufferScaleFactor = 1.0;
};

[SecureContext, Exposed=Window]
interface XRWebGLLayer: XRLayer {
  constructor(XRSession session,
             XRWebGLRenderingContext context,
             optional XRWebGLLayerInit layerInit = {});
  // Attributes
  readonly attribute boolean antialias;
  readonly attribute boolean ignoreDepthValues;
  attribute float? fixedFoveation;

  [SameObject] readonly attribute WebGLFramebuffer? framebuffer;
  readonly attribute unsigned long framebufferWidth;
  readonly attribute unsigned long framebufferHeight;

  // Methods
  XRViewport? getViewport(XRView view);

  // Static Methods
  static double getNativeFramebufferScaleFactor(XRSession session);
};

partial dictionary WebGLContextAttributes {
    boolean xrCompatible = false;
};

partial interface mixin WebGLRenderingContextBase {
    [NewObject] Promise<undefined> makeXRCompatible();
};

[SecureContext, Exposed=Window]
interface XRSessionEvent : Event {
  constructor(DOMString type, XRSessionEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
};

dictionary XRSessionEventInit : EventInit {
  required XRSession session;
};

[SecureContext, Exposed=Window]
interface XRInputSourceEvent : Event {
  constructor(DOMString type, XRInputSourceEventInit eventInitDict);
  [SameObject] readonly attribute XRFrame frame;
  [SameObject] readonly attribute XRInputSource inputSource;
};

dictionary XRInputSourceEventInit : EventInit {
  required XRFrame frame;
  required XRInputSource inputSource;
};

[SecureContext, Exposed=Window]
interface XRInputSourcesChangeEvent : Event {
  constructor(DOMString type, XRInputSourcesChangeEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
  [SameObject] readonly attribute FrozenArray<XRInputSource> added;
  [SameObject] readonly attribute FrozenArray<XRInputSource> removed;
};

dictionary XRInputSourcesChangeEventInit : EventInit {
  required XRSession session;
  required sequence<XRInputSource> added;
  required sequence<XRInputSource> removed;

};

[SecureContext, Exposed=Window]
interface XRReferenceSpaceEvent : Event {
  constructor(DOMString type, XRReferenceSpaceEventInit eventInitDict);
  [SameObject] readonly attribute XRReferenceSpace referenceSpace;
  [SameObject] readonly attribute XRRigidTransform? transform;
};

dictionary XRReferenceSpaceEventInit : EventInit {
  required XRReferenceSpace referenceSpace;
  XRRigidTransform? transform = null;
};

[SecureContext, Exposed=Window]
interface XRVisibilityMaskChangeEvent : Event {
  constructor(DOMString type, XRVisibilityMaskChangeEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
  readonly attribute XREye eye;
  readonly attribute unsigned long index;
  [SameObject] readonly attribute Float32Array vertices;
  [SameObject] readonly attribute Uint32Array indices;
};

dictionary XRVisibilityMaskChangeEventInit : EventInit {
  required XRSession session;
  required XREye eye;
  required unsigned long index;
  required Float32Array vertices;
  required Uint32Array indices;
};

dictionary XRSessionSupportedPermissionDescriptor: PermissionDescriptor {
  XRSessionMode mode;
};

dictionary XRPermissionDescriptor: PermissionDescriptor {
  XRSessionMode mode;
  sequence<DOMString> requiredFeatures;
  sequence<DOMString> optionalFeatures;
};

[Exposed=Window]
interface XRPermissionStatus: PermissionStatus {
  attribute FrozenArray<DOMString> granted;
};

MDN

Navigator/xr

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

WebGLRenderingContext/makeXRCompatible

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?

WebGLRenderingContext/makeXRCompatible

In only one current engine.

FirefoxNoneSafariNoneChrome79+
OperaNoneEdge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera MobileNone
MDN

XRBoundedReferenceSpace/boundsGeometry

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRBoundedReferenceSpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRFrame/getPose

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRFrame/getViewerPose

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRFrame/session

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRFrame

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSource/gripSpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSource/handedness

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSource/profiles

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSource/targetRayMode

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSource/targetRaySpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSource

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourceArray/length

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourceArray

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourceEvent/XRInputSourceEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourceEvent/frame

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourceEvent/inputSource

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourceEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourcesChangeEvent/XRInputSourcesChangeEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourcesChangeEvent/added

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourcesChangeEvent/removed

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourcesChangeEvent/session

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRInputSourcesChangeEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRLayer

In only one current engine.

FirefoxNoneSafariNoneChrome84+
Opera?Edge84+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRPose/angularVelocity

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRPose/emulatedPosition

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRPose/linearVelocity

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRPose/transform

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRPose

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpace/getOffsetReferenceSpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpace/reset_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpace/reset_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpaceEvent/XRReferenceSpaceEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpaceEvent/referenceSpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpaceEvent/transform

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRReferenceSpaceEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRRenderState/baseLayer

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRRenderState/depthFar

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRRenderState/depthNear

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRRenderState/inlineVerticalFieldOfView

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRRenderState

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRRigidTransform/XRRigidTransform

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRRigidTransform/inverse

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRRigidTransform/matrix

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRRigidTransform/orientation

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRRigidTransform/position

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRRigidTransform

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/cancelAnimationFrame

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/end

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/end_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/end_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/inputSources

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/inputsourceschange_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/inputsourceschange_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/renderState

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/requestAnimationFrame

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/requestReferenceSpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/select_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/select_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/selectend_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/selectend_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/selectstart_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/selectstart_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/squeeze_event

In only one current engine.

FirefoxNoneSafariNoneChrome83+
Opera?Edge83+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSession/squeeze_event

In only one current engine.

FirefoxNoneSafariNoneChrome83+
Opera?Edge83+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSession/squeezeend_event

In only one current engine.

FirefoxNoneSafariNoneChrome83+
Opera?Edge83+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSession/squeezeend_event

In only one current engine.

FirefoxNoneSafariNoneChrome83+
Opera?Edge83+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSession/squeezestart_event

In only one current engine.

FirefoxNoneSafariNoneChrome83+
Opera?Edge83+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSession/squeezestart_event

In only one current engine.

FirefoxNoneSafariNoneChrome83+
Opera?Edge83+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSession/updateRenderState

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/visibilitychange_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/visibilitychange_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession/visibilityState

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSession

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSessionEvent/XRSessionEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSessionEvent/session

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSessionEvent

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSpace

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRSystem/devicechange_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSystem/devicechange_event

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSystem/isSessionSupported

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSystem/requestSession

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRSystem

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRView/eye

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRView/recommendedViewportScale

In only one current engine.

FirefoxNoneSafariNoneChrome90+
Opera?Edge90+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRView/requestViewportScale

In only one current engine.

FirefoxNoneSafariNoneChrome90+
Opera?Edge90+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

XRView

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRViewerPose/views

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRViewerPose

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRViewport/height

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRViewport/width

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRViewport/x

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRViewport/y

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRViewport

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/XRWebGLLayer

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/antialias

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/fixedFoveation

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRWebGLLayer/framebuffer

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/framebufferHeight

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/framebufferWidth

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/getNativeFramebufferScaleFactor_static

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/getViewport

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer/ignoreDepthValues

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

XRWebGLLayer

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet11.2+Opera Mobile?
MDN

Headers/Feature-Policy/xr-spatial-tracking

In only one current engine.

FirefoxNoneSafariNoneChrome79+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera MobileNone

Headers/Permissions-Policy/xr-spatial-tracking

In only one current engine.

FirefoxNoneSafariNoneChrome88+
Opera?Edge88+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera MobileNone