资讯动态

axe-core 对 CustomElement 的 ElementInternals 支持:社区协议、源码实现与规则集成指南

发布时间:2026/9/28 2:56:18 来源:尧图企业网站定制
测试【免费下载链接】axe-coreAccessibility engine for automated Web UI testing项目地址https://gitcode.com/gh_mirrors/ax/axe-core点击查看免费下载本文基于 axe-core 仓库的 doc/element-internals.md 编写深入讲解 axe-core 如何通过社区协议读取自定义元素CustomElement的 ElementInternals 信息、这些信息如何进入虚拟节点与规则判定流程以及当前支持范围与已知限制。读完本文你将掌握在自己的自定义元素上暴露 ElementInternals 的正确写法、axe-core 底层查找这些信息的完整顺序与守卫逻辑以及当 axe-core 运行在隔离 JS 上下文时如何通过axe.externalAPIs把收集到的数据交给引擎。背景为什么 axe-core 需要社区协议Web Components 生态中自定义元素可以通过attachInternals()获得一个ElementInternals对象并借此声明 ARIA 语义如role与表单行为如labels、form这就是所谓的ARIA Properties。例如一个自定义按钮组件可以这样声明自己是rolebuttonconst internals this.attachInternals(); internals.role button;问题在于JavaScript 并不提供任何公开 API 来从一个节点反向读取它的 ElementInternals 对象。浏览器把这份信息私有保存axe-core 作为第三方无障碍检测引擎在扫描 DOM 时无法凭空得知某个自定义元素内部声明了怎样的role。因此 axe-core 必须依赖开发者在其自定义元素上实现一种社区协议——即通过约定俗成的全局映射表或公开属性主动把ElementInternals暴露出来供 axe-core 查找。axe-core 本身不做任何假设只负责按协议去取。axe-core 支持的 ElementInternals 暴露协议根据 get-element-internals.js 源码 中的注释axe-core 目前支持以下两种方式、共六种取法方式一全局 WeakMapglobalThis._elementInternals开发者维护一个全局WeakMap以元素节点为键、以ElementInternals对象为值globalThis._elementInternals ?? new WeakMap(); globalThis._elementInternals.set(this, internals);axe-core 通过globalThis._elementInternals?.get(node)直接命中查找。这是官方文档给出的首选示例因为WeakMap以对象为键不会造成内存泄漏全局单例语义清晰一个页面只维护一份映射查找成本是 O(1)且不污染元素自身属性。方式二元素上的公开属性或 Symbol除了全局映射axe-core 还支持直接在自定义元素实例上挂载以下公共属性public properties或 Symbol暴露形式类型源码常量get-element-internals.js_internals字符串属性propNames[0]internals字符串属性propNames[1]internals_字符串属性propNames[2]Symbol(internals)SymbolsymbolNames[0]Symbol(privateInternals)SymbolsymbolNames[1]文档给出的推荐写法是_internals官方注释标记为recommended因为它直观、不易与既有框架属性冲突且避免了全局状态的污染CustomElements.define( my-custom-button, class MyCustomButton extends HTMLElement { constructor() { super(); this._internals this.attachInternals(); this._internals.role button; } } );其中Symbol(internals)与Symbol(privateInternals)适合想要隐藏实现细节、防止被普通属性枚举或误读的组件库作者。源码级解析getElementInternals 的查找顺序与守卫axe-core 的入口工具函数位于 lib/core/utils/get-element-internals.js它会按严格的顺序执行查找并带有多重安全守卫export default function getElementInternals(node) { // 1. 只有合法自定义元素名才继续 if (!isValidCustomElementName(node.nodeName.toLowerCase())) { return; } // 2. 优先查全局 WeakMap const mapInternals globalThis._elementInternals?.get(node); if (mapInternals) { return mapInternals; } // 3. IE11 守卫环境不支持 ElementInternals 直接放弃 if (!(ElementInternals in window)) { return; } // 4. 依次检查三个字符串属性 for (const propName of propNames) { if (Object.getOwnPropertyDescriptor(node, propName)?.get) { continue; // 跳过 getter 定义的属性 } if (node[propName] instanceof window.ElementInternals) { return node[propName]; } } // 5. 检查两个 Symbol 属性 const ownSymbols Object.getOwnPropertySymbols(node); for (const symbolName of symbolNames) { const symbol ownSymbols.find(s s.description symbolName); if (symbol) { if (Object.getOwnPropertyDescriptor(node, symbol)?.get) { continue; } if (node[symbol] instanceof window.ElementInternals) { return node[symbol]; } } } }几个值得注意的实现细节自定义元素名校验attachInternals()只能作用于自定义元素对原生元素或 customized built-in如button is...调用会抛错。源码通过isValidCustomElementName提前拦截避免后续逻辑对非法节点误判。测试 test/core/utils/get-element-internals.js 专门覆盖了原生元素与customized built-in 元素两种必须返回undefined的场景。getter 守卫如果属性是Object.defineProperty定义的 getter而非数据属性axe-core 会跳过它。原因getter 可能在取值时产生副作用或抛错且社区协议约定的是直接赋值读取 getter 不可靠。对应测试见 test/core/utils/get-element-internals.js。类型校验属性值必须是window.ElementInternals的实例才会被接受防止开发者误把任意字符串或对象当作 internals 传入测试见 test/core/utils/get-element-internals.js。全局映射优先当元素同时出现在全局 WeakMap 和自身属性上时以全局映射为准测试见 test/core/utils/get-element-internals.js若全局映射中不存在该节点则回退到属性查找test/core/utils/get-element-internals.js。跨属性容错如果_internals是 getter 被跳过但internals是正常数据属性依然能取到test/core/utils/get-element-internals.js。数据如何进入 axe-corewalkTree 收集与虚拟节点挂载找到ElementInternals只是第一步。axe-core 在扫描页面时由 lib/gather-internals/walk-tree.js 的walkTree()完成收集使用document.createTreeWalker遍历整棵 DOM对每个元素调用getElementInternals(node)命中后记录该节点的ancestryCSS 祖先选择器由getAncestry生成用于跨上下文唯一定位节点遍历ElementInternals的可枚举属性只保留两类以aria[A-Z]开头的 ARIA 属性正则ariaPropRegex /^aria[A-Z]/如ariaLabel、ariaLabelledByElements、ariaActiveDescendantElement等白名单propsToCapture [role, labels, form]。把结果推入elementInternalsMap数组每条形如{ ancestry, internals }。对 idref / idrefs 类属性例如ariaActiveDescendantElement指向某个元素、ariaLabelledByElements指向一组元素walkTree 会把DOM 节点引用转换为 ancestry 字符串并在节点未连接到文档树时直接丢弃见 walk-tree.js 及测试 test/gather-internals/index.js。读取internals.form时若元素不是表单关联元素会抛错源码用 try/catch 静默吞掉walk-tree.js。收集完成后axe-core 在构建虚拟节点时把数据挂到 vNode 上。VirtualNode提供了带缓存的elementInternalsgetterlib/core/base/virtual-node/virtual-node.js后续所有规则与 commons 逻辑都通过vNode.elementInternals访问。规则如何消费 roleimplicitRole 与匹配器ElementInternals 的role最终参与角色计算。在 lib/commons/aria/implicit-role.js 中if (vNode.elementInternals?.role) { return vNode.elementInternals.role; }即当自定义元素通过 ElementInternals 声明了role时该 role 优先于元素名称推导出的隐式角色。同理lib/commons/matches/in-sectioning-content.js 在判断元素是否位于分区内容landmark 判定相关时也会读取elementInternals?.role作为兜底角色来源。当前支持范围与已知限制务必阅读原文档明确标注了以下边界源码行为与之完全一致仅支持role属性。ariaLabel等其他 ElementInternals 属性目前不被规则消费尽管 walkTree 会捕获以aria开头的属性但当前规则体系主要依赖role更多属性的支持已在计划中。axe-core 不校验 ElementInternalsrole的值。即使传入非法角色名也不会触发aria-valid-attr-value之类的校验。许多规则不会在带 ElementInternals 的元素上运行。典型如aria-required-attr——因为 axe-core 无法获知完整的 ARIA 状态选择直接跳过。部分规则只是部分支持。例如aria-required-children可能只能做有限判定。因此如果自定义元素完全依赖 ElementInternals 表达无障碍语义请意识到 axe-core 对它的覆盖是逐步扩展的不要期待与原生 HTML 元素同等的检测深度。进阶隔离 JS 上下文下的 externalAPIs 集成社区协议依赖开发者把 internals 暴露出来 axe-core 直接遍历 DOM。但在浏览器扩展等场景中axe-core 可能运行在隔离的 JS 上下文如扩展的 content script此时attachInternals的实例与 DOM 遍历都可能不可用。axe-core 为此提供了axe.externalAPIs({ elementInternals })入口lib/core/public/external-apis.js注册于 lib/core/core.jsaxe.externalAPIs({ elementInternals() { return Promise.resolve([ { // CSS ancestry 选择器可由 axe.utils.getAncestry(node) 生成 ancestry: html body main my-custom-button, internals: { role: button } } ]); } });elementInternals必须返回一个 Promiseresolve 出一个数组每个元素包含ancestry字符串或字符串数组用于 Shadow DOM 场景与internals对象属性值需自行序列化idref 类属性用{ type: HTMLElement, value: ancestry }或{ type: NodeList, value: [ancestry] }表达。同时可配置elementInternalsTimeout默认 1000ms。若 Promise 超时axe-core 将直接抛错external-apis.js。传入的数据会在loadElementInternals中经结构校验后通过shadowSelectgetNodeFromTree定位节点再反向把internals挂到对应 vNode 上external-apis.js。仓库还提供了可直接注入主上下文的收集脚本gather-internals.js构建产物源码即 lib/gather-internals/main.js在扩展中可用chrome.scripting.executeScript({ files: [gather-internals.js], world: MAIN })注入后把返回的elementInternalsMap数组直接作为 Promise 的 resolve 值。完整的配置说明见 doc/external-apis.md。验证与测试仓库为这套机制提供了完备的测试可作为你接入时的行为基准test/core/utils/get-element-internals.js覆盖全局映射、三种字符串属性、两种 Symbol、getter 跳过、类型校验、ElementInternals不存在守卫、原生元素与 customized built-in 拒绝等全部边界。test/gather-internals/index.js覆盖 walkTree 的收集结果结构、嵌套元素、Shadow DOM 内元素、ancestry 生成、idref/idrefs 解析与未连接节点丢弃、form与labels捕获以及无需 axe 环境也能运行的独立性验证。小结axe-core 对 CustomElement ElementInternals 的支持是一套约定优于配置的务实方案通过社区协议把浏览器不公开的私有状态显式暴露出来再用getElementInternals → walkTree → vNode.elementInternals → implicitRole/匹配器这条完整链路把role纳入规则判定。如果你在开发无障碍组件库建议优先采用_internals属性或全局 WeakMap 两种协议之一如果你的 axe-core 跑在隔离上下文则直接使用axe.externalAPIs({ elementInternals })传数据。同时牢记当前仅role受支持、值不校验、部分规则不运行的边界合理规划检测覆盖预期。赞分享测试【免费下载链接】axe-coreAccessibility engine for automated Web UI testing项目地址https://gitcode.com/gh_mirrors/ax/axe-core点击查看免费下载相关推荐gh_mirrors/core72/core社区指南贡献代码与获取支持gh_mirrors/core72/core社区指南贡献代码与获取支持 你是否在使用Online IDE时遇到问题不知如何反馈想为项目贡献代码却找不到入门路LocalSend WebRTC集成实时通信协议支持与实现LocalSend WebRTC集成实时通信协议支持与实现 引言为什么需要WebRTC 在现代分布式应用中实时通信Real time Communic即时通讯网络/通信A2A 协议社区全景指南生态集成、社区 SDK 与参与路径A2A 协议社区全景指南生态集成、社区 SDK 与参与路径 Agent2AgentA2A是一个开放、标准化的协议用于实现不同框架、不同厂商之间 AI 智人工智能AI AgentAPI设计上一篇Hermes Agent连接池参数怎么配4 个指标验证调优生效下一篇tModLoader模组热重载技术如何在不重启游戏的情况下测试模组创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑