mirror of
https://github.com/kuaifan/dootask.git
synced 2026-07-28 17:06:00 +00:00
页面操作(doo page → /ws → 浮窗 operation-module → action-executor)原用手写 iView-only 选择器 + 朴素 value= 赋值,在 React+Radix 同源 iframe 业务表单上几乎 填不进。重构为「复用 Playwright 注入脚本的描述层 + 按环境选后端的执行层」: 描述层(共用): - 新增 aria/aria-bundle.js,vendoring Playwright ariaSnapshot/roleUtils/domUtils 子集预构建为 ESM(Apache-2.0 NOTICE + 固定上游 commit),产出 YAML 快照与活的 ref→Element Map。 - page-context-collector 改用 buildSnapshot 采集,operation-module 经 setRefMap 以 Map 直接持有元素取代 selector 反查。 执行层(按环境选后端,失败回退): - web-backend:原生 prototype value setter + beforeinput/input/change/blur 序列 + 回读校验(不符报 value_not_applied,绝不假报成功);自定义下拉/日期 open→click。 - electron-backend + electron/lib/page-input.js:经 webContents.debugger 走 CDP Input 域产生 isTrusted=true 可信输入(insertText/dispatchMouseEvent/KeyEvent), 任一步失败回退 Web 后端。 - 复杂控件(动态明细表/成员选择器/文件上传)如实返回 unsupported_widget。 协议与链路(doo page、active-context 失效守卫、导航类 action)保持不变。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2564 lines
82 KiB
JavaScript
Vendored
2564 lines
82 KiB
JavaScript
Vendored
/*
|
|
* Vendored subset of Playwright injected ariaSnapshot (Apache-2.0).
|
|
* Source: github.com/microsoft/playwright @ aa3c7b5 (packages/injected/src/{ariaSnapshot,roleUtils,domUtils}.ts + packages/isomorphic/*).
|
|
* Built via esbuild from esm-entry.ts. Do not edit by hand; regenerate to update.
|
|
* See dootask/docs/ai-page-operation-unified-executor.md.
|
|
*/
|
|
|
|
// packages/isomorphic/ariaSnapshot.ts
|
|
function ariaNodesEqual(a, b) {
|
|
if (a.role !== b.role || a.name !== b.name)
|
|
return false;
|
|
if (!ariaPropsEqual(a, b) || hasPointerCursor(a) !== hasPointerCursor(b))
|
|
return false;
|
|
const aKeys = Object.keys(a.props);
|
|
const bKeys = Object.keys(b.props);
|
|
return aKeys.length === bKeys.length && aKeys.every((k) => a.props[k] === b.props[k]);
|
|
}
|
|
function hasPointerCursor(ariaNode) {
|
|
return ariaNode.box.cursor === "pointer";
|
|
}
|
|
function ariaPropsEqual(a, b) {
|
|
return a.active === b.active && a.checked === b.checked && a.disabled === b.disabled && a.expanded === b.expanded && a.invalid === b.invalid && a.selected === b.selected && a.level === b.level && a.pressed === b.pressed;
|
|
}
|
|
|
|
// packages/isomorphic/stringUtils.ts
|
|
var normalizedWhitespaceCache;
|
|
function normalizeWhiteSpace(text) {
|
|
let result = normalizedWhitespaceCache?.get(text);
|
|
if (result === void 0) {
|
|
result = text.replace(/[\u200b\u00ad]/g, "").trim().replace(/\s+/g, " ");
|
|
normalizedWhitespaceCache?.set(text, result);
|
|
}
|
|
return result;
|
|
}
|
|
function escapeRegExp(s) {
|
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
function longestCommonSubstring(s1, s2) {
|
|
const n = s1.length;
|
|
const m = s2.length;
|
|
let maxLen = 0;
|
|
let endingIndex = 0;
|
|
const dp = Array(n + 1).fill(null).map(() => Array(m + 1).fill(0));
|
|
for (let i = 1; i <= n; i++) {
|
|
for (let j = 1; j <= m; j++) {
|
|
if (s1[i - 1] === s2[j - 1]) {
|
|
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
if (dp[i][j] > maxLen) {
|
|
maxLen = dp[i][j];
|
|
endingIndex = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return s1.slice(endingIndex - maxLen, endingIndex);
|
|
}
|
|
var ansiRegex = new RegExp("([\\u001B\\u009B][[\\]()#?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{0,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))", "g");
|
|
|
|
// packages/isomorphic/yaml.ts
|
|
function yamlEscapeKeyIfNeeded(str) {
|
|
if (!yamlStringNeedsQuotes(str))
|
|
return str;
|
|
return `'` + str.replace(/'/g, `''`) + `'`;
|
|
}
|
|
function yamlEscapeValueIfNeeded(str) {
|
|
if (!yamlStringNeedsQuotes(str))
|
|
return str;
|
|
return '"' + str.replace(/[\\"\x00-\x1f\x7f-\x9f]/g, (c) => {
|
|
switch (c) {
|
|
case "\\":
|
|
return "\\\\";
|
|
case '"':
|
|
return '\\"';
|
|
case "\b":
|
|
return "\\b";
|
|
case "\f":
|
|
return "\\f";
|
|
case "\n":
|
|
return "\\n";
|
|
case "\r":
|
|
return "\\r";
|
|
case " ":
|
|
return "\\t";
|
|
default:
|
|
const code = c.charCodeAt(0);
|
|
return "\\x" + code.toString(16).padStart(2, "0");
|
|
}
|
|
}) + '"';
|
|
}
|
|
function yamlStringNeedsQuotes(str) {
|
|
if (str.length === 0)
|
|
return true;
|
|
if (/^\s|\s$/.test(str))
|
|
return true;
|
|
if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/.test(str))
|
|
return true;
|
|
if (/^-/.test(str))
|
|
return true;
|
|
if (/[\n:](\s|$)/.test(str))
|
|
return true;
|
|
if (/\s#/.test(str))
|
|
return true;
|
|
if (/[\n\r]/.test(str))
|
|
return true;
|
|
if (/^[&*\],?!>|@"'#%]/.test(str))
|
|
return true;
|
|
if (/[{}`]/.test(str))
|
|
return true;
|
|
if (/^\[/.test(str))
|
|
return true;
|
|
if (!isNaN(Number(str)) || ["y", "n", "yes", "no", "true", "false", "on", "off", "null"].includes(str.toLowerCase()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// packages/injected/src/domUtils.ts
|
|
var globalOptions = {};
|
|
function parentElementOrShadowHost(element) {
|
|
if (element.parentElement)
|
|
return element.parentElement;
|
|
if (!element.parentNode)
|
|
return;
|
|
if (element.parentNode.nodeType === 11 && element.parentNode.host)
|
|
return element.parentNode.host;
|
|
}
|
|
function enclosingShadowRootOrDocument(element) {
|
|
let node = element;
|
|
while (node.parentNode)
|
|
node = node.parentNode;
|
|
if (node.nodeType === 11 || node.nodeType === 9)
|
|
return node;
|
|
}
|
|
function enclosingShadowHost(element) {
|
|
while (element.parentElement)
|
|
element = element.parentElement;
|
|
return parentElementOrShadowHost(element);
|
|
}
|
|
function closestCrossShadow(element, css, scope) {
|
|
while (element) {
|
|
const closest = element.closest(css);
|
|
if (scope && closest !== scope && closest?.contains(scope))
|
|
return;
|
|
if (closest)
|
|
return closest;
|
|
element = enclosingShadowHost(element);
|
|
}
|
|
}
|
|
function getElementComputedStyle(element, pseudo) {
|
|
const cache = pseudo === "::before" ? cacheStyleBefore : pseudo === "::after" ? cacheStyleAfter : cacheStyle;
|
|
if (cache && cache.has(element))
|
|
return cache.get(element);
|
|
const style = element.ownerDocument && element.ownerDocument.defaultView ? element.ownerDocument.defaultView.getComputedStyle(element, pseudo) : void 0;
|
|
cache?.set(element, style);
|
|
return style;
|
|
}
|
|
function isElementStyleVisibilityVisible(element, style) {
|
|
style = style ?? getElementComputedStyle(element);
|
|
if (!style)
|
|
return true;
|
|
if (Element.prototype.checkVisibility && globalOptions.browserNameForWorkarounds !== "webkit") {
|
|
if (!element.checkVisibility())
|
|
return false;
|
|
} else {
|
|
const detailsOrSummary = element.closest("details,summary");
|
|
if (detailsOrSummary !== element && detailsOrSummary?.nodeName === "DETAILS" && !detailsOrSummary.open)
|
|
return false;
|
|
}
|
|
if (style.visibility !== "visible")
|
|
return false;
|
|
return true;
|
|
}
|
|
function computeBox(element) {
|
|
const style = getElementComputedStyle(element);
|
|
if (!style)
|
|
return { visible: true, inline: false };
|
|
const cursor = style.cursor;
|
|
if (style.display === "contents") {
|
|
for (let child = element.firstChild; child; child = child.nextSibling) {
|
|
if (child.nodeType === 1 && isElementVisible(child))
|
|
return { visible: true, inline: false, cursor };
|
|
if (child.nodeType === 3 && isVisibleTextNode(child))
|
|
return { visible: true, inline: true, cursor };
|
|
}
|
|
return { visible: false, inline: false, cursor };
|
|
}
|
|
if (!isElementStyleVisibilityVisible(element, style))
|
|
return { cursor, visible: false, inline: false };
|
|
const rect = element.getBoundingClientRect();
|
|
return { cursor, visible: rect.width > 0 && rect.height > 0, inline: style.display === "inline" };
|
|
}
|
|
function isElementVisible(element) {
|
|
return computeBox(element).visible;
|
|
}
|
|
function isVisibleTextNode(node) {
|
|
const range = node.ownerDocument.createRange();
|
|
range.selectNode(node);
|
|
const rect = range.getBoundingClientRect();
|
|
return rect.width > 0 && rect.height > 0;
|
|
}
|
|
function elementSafeTagName(element) {
|
|
const tagName = element.tagName;
|
|
if (typeof tagName === "string")
|
|
return tagName.toUpperCase();
|
|
if (element instanceof HTMLFormElement)
|
|
return "FORM";
|
|
return element.tagName.toUpperCase();
|
|
}
|
|
var cacheStyle;
|
|
var cacheStyleBefore;
|
|
var cacheStyleAfter;
|
|
var cachesCounter = 0;
|
|
function beginDOMCaches() {
|
|
++cachesCounter;
|
|
cacheStyle ??= /* @__PURE__ */ new Map();
|
|
cacheStyleBefore ??= /* @__PURE__ */ new Map();
|
|
cacheStyleAfter ??= /* @__PURE__ */ new Map();
|
|
}
|
|
function endDOMCaches() {
|
|
if (!--cachesCounter) {
|
|
cacheStyle = void 0;
|
|
cacheStyleBefore = void 0;
|
|
cacheStyleAfter = void 0;
|
|
}
|
|
}
|
|
|
|
// packages/isomorphic/cssTokenizer.ts
|
|
var between = function(num, first, last) {
|
|
return num >= first && num <= last;
|
|
};
|
|
function digit(code) {
|
|
return between(code, 48, 57);
|
|
}
|
|
function hexdigit(code) {
|
|
return digit(code) || between(code, 65, 70) || between(code, 97, 102);
|
|
}
|
|
function uppercaseletter(code) {
|
|
return between(code, 65, 90);
|
|
}
|
|
function lowercaseletter(code) {
|
|
return between(code, 97, 122);
|
|
}
|
|
function letter(code) {
|
|
return uppercaseletter(code) || lowercaseletter(code);
|
|
}
|
|
function nonascii(code) {
|
|
return code >= 128;
|
|
}
|
|
function namestartchar(code) {
|
|
return letter(code) || nonascii(code) || code === 95;
|
|
}
|
|
function namechar(code) {
|
|
return namestartchar(code) || digit(code) || code === 45;
|
|
}
|
|
function nonprintable(code) {
|
|
return between(code, 0, 8) || code === 11 || between(code, 14, 31) || code === 127;
|
|
}
|
|
function newline(code) {
|
|
return code === 10;
|
|
}
|
|
function whitespace(code) {
|
|
return newline(code) || code === 9 || code === 32;
|
|
}
|
|
var maximumallowedcodepoint = 1114111;
|
|
var InvalidCharacterError = class extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = "InvalidCharacterError";
|
|
}
|
|
};
|
|
function preprocess(str) {
|
|
const codepoints = [];
|
|
for (let i = 0; i < str.length; i++) {
|
|
let code = str.charCodeAt(i);
|
|
if (code === 13 && str.charCodeAt(i + 1) === 10) {
|
|
code = 10;
|
|
i++;
|
|
}
|
|
if (code === 13 || code === 12)
|
|
code = 10;
|
|
if (code === 0)
|
|
code = 65533;
|
|
if (between(code, 55296, 56319) && between(str.charCodeAt(i + 1), 56320, 57343)) {
|
|
const lead = code - 55296;
|
|
const trail = str.charCodeAt(i + 1) - 56320;
|
|
code = Math.pow(2, 16) + lead * Math.pow(2, 10) + trail;
|
|
i++;
|
|
}
|
|
codepoints.push(code);
|
|
}
|
|
return codepoints;
|
|
}
|
|
function stringFromCode(code) {
|
|
if (code <= 65535)
|
|
return String.fromCharCode(code);
|
|
code -= Math.pow(2, 16);
|
|
const lead = Math.floor(code / Math.pow(2, 10)) + 55296;
|
|
const trail = code % Math.pow(2, 10) + 56320;
|
|
return String.fromCharCode(lead) + String.fromCharCode(trail);
|
|
}
|
|
function tokenize(str1) {
|
|
const str = preprocess(str1);
|
|
let i = -1;
|
|
const tokens = [];
|
|
let code;
|
|
let line = 0;
|
|
let column = 0;
|
|
let lastLineLength = 0;
|
|
const incrLineno = function() {
|
|
line += 1;
|
|
lastLineLength = column;
|
|
column = 0;
|
|
};
|
|
const locStart = { line, column };
|
|
const codepoint = function(i2) {
|
|
if (i2 >= str.length)
|
|
return -1;
|
|
return str[i2];
|
|
};
|
|
const next = function(num) {
|
|
if (num === void 0)
|
|
num = 1;
|
|
if (num > 3)
|
|
throw "Spec Error: no more than three codepoints of lookahead.";
|
|
return codepoint(i + num);
|
|
};
|
|
const consume = function(num) {
|
|
if (num === void 0)
|
|
num = 1;
|
|
i += num;
|
|
code = codepoint(i);
|
|
if (newline(code))
|
|
incrLineno();
|
|
else
|
|
column += num;
|
|
return true;
|
|
};
|
|
const reconsume = function() {
|
|
i -= 1;
|
|
if (newline(code)) {
|
|
line -= 1;
|
|
column = lastLineLength;
|
|
} else {
|
|
column -= 1;
|
|
}
|
|
locStart.line = line;
|
|
locStart.column = column;
|
|
return true;
|
|
};
|
|
const eof = function(codepoint2) {
|
|
if (codepoint2 === void 0)
|
|
codepoint2 = code;
|
|
return codepoint2 === -1;
|
|
};
|
|
const donothing = function() {
|
|
};
|
|
const parseerror = function() {
|
|
};
|
|
const consumeAToken = function() {
|
|
consumeComments();
|
|
consume();
|
|
if (whitespace(code)) {
|
|
while (whitespace(next()))
|
|
consume();
|
|
return new WhitespaceToken();
|
|
} else if (code === 34) {
|
|
return consumeAStringToken();
|
|
} else if (code === 35) {
|
|
if (namechar(next()) || areAValidEscape(next(1), next(2))) {
|
|
const token = new HashToken("");
|
|
if (wouldStartAnIdentifier(next(1), next(2), next(3)))
|
|
token.type = "id";
|
|
token.value = consumeAName();
|
|
return token;
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 36) {
|
|
if (next() === 61) {
|
|
consume();
|
|
return new SuffixMatchToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 39) {
|
|
return consumeAStringToken();
|
|
} else if (code === 40) {
|
|
return new OpenParenToken();
|
|
} else if (code === 41) {
|
|
return new CloseParenToken();
|
|
} else if (code === 42) {
|
|
if (next() === 61) {
|
|
consume();
|
|
return new SubstringMatchToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 43) {
|
|
if (startsWithANumber()) {
|
|
reconsume();
|
|
return consumeANumericToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 44) {
|
|
return new CommaToken();
|
|
} else if (code === 45) {
|
|
if (startsWithANumber()) {
|
|
reconsume();
|
|
return consumeANumericToken();
|
|
} else if (next(1) === 45 && next(2) === 62) {
|
|
consume(2);
|
|
return new CDCToken();
|
|
} else if (startsWithAnIdentifier()) {
|
|
reconsume();
|
|
return consumeAnIdentlikeToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 46) {
|
|
if (startsWithANumber()) {
|
|
reconsume();
|
|
return consumeANumericToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 58) {
|
|
return new ColonToken();
|
|
} else if (code === 59) {
|
|
return new SemicolonToken();
|
|
} else if (code === 60) {
|
|
if (next(1) === 33 && next(2) === 45 && next(3) === 45) {
|
|
consume(3);
|
|
return new CDOToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 64) {
|
|
if (wouldStartAnIdentifier(next(1), next(2), next(3)))
|
|
return new AtKeywordToken(consumeAName());
|
|
else
|
|
return new DelimToken(code);
|
|
} else if (code === 91) {
|
|
return new OpenSquareToken();
|
|
} else if (code === 92) {
|
|
if (startsWithAValidEscape()) {
|
|
reconsume();
|
|
return consumeAnIdentlikeToken();
|
|
} else {
|
|
parseerror();
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 93) {
|
|
return new CloseSquareToken();
|
|
} else if (code === 94) {
|
|
if (next() === 61) {
|
|
consume();
|
|
return new PrefixMatchToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 123) {
|
|
return new OpenCurlyToken();
|
|
} else if (code === 124) {
|
|
if (next() === 61) {
|
|
consume();
|
|
return new DashMatchToken();
|
|
} else if (next() === 124) {
|
|
consume();
|
|
return new ColumnToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (code === 125) {
|
|
return new CloseCurlyToken();
|
|
} else if (code === 126) {
|
|
if (next() === 61) {
|
|
consume();
|
|
return new IncludeMatchToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
} else if (digit(code)) {
|
|
reconsume();
|
|
return consumeANumericToken();
|
|
} else if (namestartchar(code)) {
|
|
reconsume();
|
|
return consumeAnIdentlikeToken();
|
|
} else if (eof()) {
|
|
return new EOFToken();
|
|
} else {
|
|
return new DelimToken(code);
|
|
}
|
|
};
|
|
const consumeComments = function() {
|
|
while (next(1) === 47 && next(2) === 42) {
|
|
consume(2);
|
|
while (true) {
|
|
consume();
|
|
if (code === 42 && next() === 47) {
|
|
consume();
|
|
break;
|
|
} else if (eof()) {
|
|
parseerror();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const consumeANumericToken = function() {
|
|
const num = consumeANumber();
|
|
if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
|
|
const token = new DimensionToken();
|
|
token.value = num.value;
|
|
token.repr = num.repr;
|
|
token.type = num.type;
|
|
token.unit = consumeAName();
|
|
return token;
|
|
} else if (next() === 37) {
|
|
consume();
|
|
const token = new PercentageToken();
|
|
token.value = num.value;
|
|
token.repr = num.repr;
|
|
return token;
|
|
} else {
|
|
const token = new NumberToken();
|
|
token.value = num.value;
|
|
token.repr = num.repr;
|
|
token.type = num.type;
|
|
return token;
|
|
}
|
|
};
|
|
const consumeAnIdentlikeToken = function() {
|
|
const str2 = consumeAName();
|
|
if (str2.toLowerCase() === "url" && next() === 40) {
|
|
consume();
|
|
while (whitespace(next(1)) && whitespace(next(2)))
|
|
consume();
|
|
if (next() === 34 || next() === 39)
|
|
return new FunctionToken(str2);
|
|
else if (whitespace(next()) && (next(2) === 34 || next(2) === 39))
|
|
return new FunctionToken(str2);
|
|
else
|
|
return consumeAURLToken();
|
|
} else if (next() === 40) {
|
|
consume();
|
|
return new FunctionToken(str2);
|
|
} else {
|
|
return new IdentToken(str2);
|
|
}
|
|
};
|
|
const consumeAStringToken = function(endingCodePoint) {
|
|
if (endingCodePoint === void 0)
|
|
endingCodePoint = code;
|
|
let string = "";
|
|
while (consume()) {
|
|
if (code === endingCodePoint || eof()) {
|
|
return new StringToken(string);
|
|
} else if (newline(code)) {
|
|
parseerror();
|
|
reconsume();
|
|
return new BadStringToken();
|
|
} else if (code === 92) {
|
|
if (eof(next()))
|
|
donothing();
|
|
else if (newline(next()))
|
|
consume();
|
|
else
|
|
string += stringFromCode(consumeEscape());
|
|
} else {
|
|
string += stringFromCode(code);
|
|
}
|
|
}
|
|
throw new Error("Internal error");
|
|
};
|
|
const consumeAURLToken = function() {
|
|
const token = new URLToken("");
|
|
while (whitespace(next()))
|
|
consume();
|
|
if (eof(next()))
|
|
return token;
|
|
while (consume()) {
|
|
if (code === 41 || eof()) {
|
|
return token;
|
|
} else if (whitespace(code)) {
|
|
while (whitespace(next()))
|
|
consume();
|
|
if (next() === 41 || eof(next())) {
|
|
consume();
|
|
return token;
|
|
} else {
|
|
consumeTheRemnantsOfABadURL();
|
|
return new BadURLToken();
|
|
}
|
|
} else if (code === 34 || code === 39 || code === 40 || nonprintable(code)) {
|
|
parseerror();
|
|
consumeTheRemnantsOfABadURL();
|
|
return new BadURLToken();
|
|
} else if (code === 92) {
|
|
if (startsWithAValidEscape()) {
|
|
token.value += stringFromCode(consumeEscape());
|
|
} else {
|
|
parseerror();
|
|
consumeTheRemnantsOfABadURL();
|
|
return new BadURLToken();
|
|
}
|
|
} else {
|
|
token.value += stringFromCode(code);
|
|
}
|
|
}
|
|
throw new Error("Internal error");
|
|
};
|
|
const consumeEscape = function() {
|
|
consume();
|
|
if (hexdigit(code)) {
|
|
const digits = [code];
|
|
for (let total = 0; total < 5; total++) {
|
|
if (hexdigit(next())) {
|
|
consume();
|
|
digits.push(code);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (whitespace(next()))
|
|
consume();
|
|
let value = parseInt(digits.map(function(x) {
|
|
return String.fromCharCode(x);
|
|
}).join(""), 16);
|
|
if (value > maximumallowedcodepoint)
|
|
value = 65533;
|
|
return value;
|
|
} else if (eof()) {
|
|
return 65533;
|
|
} else {
|
|
return code;
|
|
}
|
|
};
|
|
const areAValidEscape = function(c1, c2) {
|
|
if (c1 !== 92)
|
|
return false;
|
|
if (newline(c2))
|
|
return false;
|
|
return true;
|
|
};
|
|
const startsWithAValidEscape = function() {
|
|
return areAValidEscape(code, next());
|
|
};
|
|
const wouldStartAnIdentifier = function(c1, c2, c3) {
|
|
if (c1 === 45)
|
|
return namestartchar(c2) || c2 === 45 || areAValidEscape(c2, c3);
|
|
else if (namestartchar(c1))
|
|
return true;
|
|
else if (c1 === 92)
|
|
return areAValidEscape(c1, c2);
|
|
else
|
|
return false;
|
|
};
|
|
const startsWithAnIdentifier = function() {
|
|
return wouldStartAnIdentifier(code, next(1), next(2));
|
|
};
|
|
const wouldStartANumber = function(c1, c2, c3) {
|
|
if (c1 === 43 || c1 === 45) {
|
|
if (digit(c2))
|
|
return true;
|
|
if (c2 === 46 && digit(c3))
|
|
return true;
|
|
return false;
|
|
} else if (c1 === 46) {
|
|
if (digit(c2))
|
|
return true;
|
|
return false;
|
|
} else if (digit(c1)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
const startsWithANumber = function() {
|
|
return wouldStartANumber(code, next(1), next(2));
|
|
};
|
|
const consumeAName = function() {
|
|
let result = "";
|
|
while (consume()) {
|
|
if (namechar(code)) {
|
|
result += stringFromCode(code);
|
|
} else if (startsWithAValidEscape()) {
|
|
result += stringFromCode(consumeEscape());
|
|
} else {
|
|
reconsume();
|
|
return result;
|
|
}
|
|
}
|
|
throw new Error("Internal parse error");
|
|
};
|
|
const consumeANumber = function() {
|
|
let repr = "";
|
|
let type = "integer";
|
|
if (next() === 43 || next() === 45) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
}
|
|
while (digit(next())) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
}
|
|
if (next(1) === 46 && digit(next(2))) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
type = "number";
|
|
while (digit(next())) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
}
|
|
}
|
|
const c1 = next(1);
|
|
const c2 = next(2);
|
|
const c3 = next(3);
|
|
if ((c1 === 69 || c1 === 101) && digit(c2)) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
type = "number";
|
|
while (digit(next())) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
}
|
|
} else if ((c1 === 69 || c1 === 101) && (c2 === 43 || c2 === 45) && digit(c3)) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
type = "number";
|
|
while (digit(next())) {
|
|
consume();
|
|
repr += stringFromCode(code);
|
|
}
|
|
}
|
|
const value = convertAStringToANumber(repr);
|
|
return { type, value, repr };
|
|
};
|
|
const convertAStringToANumber = function(string) {
|
|
return +string;
|
|
};
|
|
const consumeTheRemnantsOfABadURL = function() {
|
|
while (consume()) {
|
|
if (code === 41 || eof()) {
|
|
return;
|
|
} else if (startsWithAValidEscape()) {
|
|
consumeEscape();
|
|
donothing();
|
|
} else {
|
|
donothing();
|
|
}
|
|
}
|
|
};
|
|
let iterationCount = 0;
|
|
while (!eof(next())) {
|
|
tokens.push(consumeAToken());
|
|
iterationCount++;
|
|
if (iterationCount > str.length * 2)
|
|
throw new Error("I'm infinite-looping!");
|
|
}
|
|
return tokens;
|
|
}
|
|
var CSSParserToken = class {
|
|
tokenType = "";
|
|
value;
|
|
toJSON() {
|
|
return { token: this.tokenType };
|
|
}
|
|
toString() {
|
|
return this.tokenType;
|
|
}
|
|
toSource() {
|
|
return "" + this;
|
|
}
|
|
};
|
|
var BadStringToken = class extends CSSParserToken {
|
|
tokenType = "BADSTRING";
|
|
};
|
|
var BadURLToken = class extends CSSParserToken {
|
|
tokenType = "BADURL";
|
|
};
|
|
var WhitespaceToken = class extends CSSParserToken {
|
|
tokenType = "WHITESPACE";
|
|
toString() {
|
|
return "WS";
|
|
}
|
|
toSource() {
|
|
return " ";
|
|
}
|
|
};
|
|
var CDOToken = class extends CSSParserToken {
|
|
tokenType = "CDO";
|
|
toSource() {
|
|
return "<!--";
|
|
}
|
|
};
|
|
var CDCToken = class extends CSSParserToken {
|
|
tokenType = "CDC";
|
|
toSource() {
|
|
return "-->";
|
|
}
|
|
};
|
|
var ColonToken = class extends CSSParserToken {
|
|
tokenType = ":";
|
|
};
|
|
var SemicolonToken = class extends CSSParserToken {
|
|
tokenType = ";";
|
|
};
|
|
var CommaToken = class extends CSSParserToken {
|
|
tokenType = ",";
|
|
};
|
|
var GroupingToken = class extends CSSParserToken {
|
|
value = "";
|
|
mirror = "";
|
|
};
|
|
var OpenCurlyToken = class extends GroupingToken {
|
|
tokenType = "{";
|
|
constructor() {
|
|
super();
|
|
this.value = "{";
|
|
this.mirror = "}";
|
|
}
|
|
};
|
|
var CloseCurlyToken = class extends GroupingToken {
|
|
tokenType = "}";
|
|
constructor() {
|
|
super();
|
|
this.value = "}";
|
|
this.mirror = "{";
|
|
}
|
|
};
|
|
var OpenSquareToken = class extends GroupingToken {
|
|
tokenType = "[";
|
|
constructor() {
|
|
super();
|
|
this.value = "[";
|
|
this.mirror = "]";
|
|
}
|
|
};
|
|
var CloseSquareToken = class extends GroupingToken {
|
|
tokenType = "]";
|
|
constructor() {
|
|
super();
|
|
this.value = "]";
|
|
this.mirror = "[";
|
|
}
|
|
};
|
|
var OpenParenToken = class extends GroupingToken {
|
|
tokenType = "(";
|
|
constructor() {
|
|
super();
|
|
this.value = "(";
|
|
this.mirror = ")";
|
|
}
|
|
};
|
|
var CloseParenToken = class extends GroupingToken {
|
|
tokenType = ")";
|
|
constructor() {
|
|
super();
|
|
this.value = ")";
|
|
this.mirror = "(";
|
|
}
|
|
};
|
|
var IncludeMatchToken = class extends CSSParserToken {
|
|
tokenType = "~=";
|
|
};
|
|
var DashMatchToken = class extends CSSParserToken {
|
|
tokenType = "|=";
|
|
};
|
|
var PrefixMatchToken = class extends CSSParserToken {
|
|
tokenType = "^=";
|
|
};
|
|
var SuffixMatchToken = class extends CSSParserToken {
|
|
tokenType = "$=";
|
|
};
|
|
var SubstringMatchToken = class extends CSSParserToken {
|
|
tokenType = "*=";
|
|
};
|
|
var ColumnToken = class extends CSSParserToken {
|
|
tokenType = "||";
|
|
};
|
|
var EOFToken = class extends CSSParserToken {
|
|
tokenType = "EOF";
|
|
toSource() {
|
|
return "";
|
|
}
|
|
};
|
|
var DelimToken = class extends CSSParserToken {
|
|
tokenType = "DELIM";
|
|
value = "";
|
|
constructor(code) {
|
|
super();
|
|
this.value = stringFromCode(code);
|
|
}
|
|
toString() {
|
|
return "DELIM(" + this.value + ")";
|
|
}
|
|
toJSON() {
|
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
json.value = this.value;
|
|
return json;
|
|
}
|
|
toSource() {
|
|
if (this.value === "\\")
|
|
return "\\\n";
|
|
else
|
|
return this.value;
|
|
}
|
|
};
|
|
var StringValuedToken = class extends CSSParserToken {
|
|
value = "";
|
|
ASCIIMatch(str) {
|
|
return this.value.toLowerCase() === str.toLowerCase();
|
|
}
|
|
toJSON() {
|
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
json.value = this.value;
|
|
return json;
|
|
}
|
|
};
|
|
var IdentToken = class extends StringValuedToken {
|
|
constructor(val) {
|
|
super();
|
|
this.value = val;
|
|
}
|
|
tokenType = "IDENT";
|
|
toString() {
|
|
return "IDENT(" + this.value + ")";
|
|
}
|
|
toSource() {
|
|
return escapeIdent(this.value);
|
|
}
|
|
};
|
|
var FunctionToken = class extends StringValuedToken {
|
|
tokenType = "FUNCTION";
|
|
mirror;
|
|
constructor(val) {
|
|
super();
|
|
this.value = val;
|
|
this.mirror = ")";
|
|
}
|
|
toString() {
|
|
return "FUNCTION(" + this.value + ")";
|
|
}
|
|
toSource() {
|
|
return escapeIdent(this.value) + "(";
|
|
}
|
|
};
|
|
var AtKeywordToken = class extends StringValuedToken {
|
|
tokenType = "AT-KEYWORD";
|
|
constructor(val) {
|
|
super();
|
|
this.value = val;
|
|
}
|
|
toString() {
|
|
return "AT(" + this.value + ")";
|
|
}
|
|
toSource() {
|
|
return "@" + escapeIdent(this.value);
|
|
}
|
|
};
|
|
var HashToken = class extends StringValuedToken {
|
|
tokenType = "HASH";
|
|
type;
|
|
constructor(val) {
|
|
super();
|
|
this.value = val;
|
|
this.type = "unrestricted";
|
|
}
|
|
toString() {
|
|
return "HASH(" + this.value + ")";
|
|
}
|
|
toJSON() {
|
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
json.value = this.value;
|
|
json.type = this.type;
|
|
return json;
|
|
}
|
|
toSource() {
|
|
if (this.type === "id")
|
|
return "#" + escapeIdent(this.value);
|
|
else
|
|
return "#" + escapeHash(this.value);
|
|
}
|
|
};
|
|
var StringToken = class extends StringValuedToken {
|
|
tokenType = "STRING";
|
|
constructor(val) {
|
|
super();
|
|
this.value = val;
|
|
}
|
|
toString() {
|
|
return '"' + escapeString(this.value) + '"';
|
|
}
|
|
};
|
|
var URLToken = class extends StringValuedToken {
|
|
tokenType = "URL";
|
|
constructor(val) {
|
|
super();
|
|
this.value = val;
|
|
}
|
|
toString() {
|
|
return "URL(" + this.value + ")";
|
|
}
|
|
toSource() {
|
|
return 'url("' + escapeString(this.value) + '")';
|
|
}
|
|
};
|
|
var NumberToken = class extends CSSParserToken {
|
|
tokenType = "NUMBER";
|
|
type;
|
|
repr;
|
|
constructor() {
|
|
super();
|
|
this.type = "integer";
|
|
this.repr = "";
|
|
}
|
|
toString() {
|
|
if (this.type === "integer")
|
|
return "INT(" + this.value + ")";
|
|
return "NUMBER(" + this.value + ")";
|
|
}
|
|
toJSON() {
|
|
const json = super.toJSON();
|
|
json.value = this.value;
|
|
json.type = this.type;
|
|
json.repr = this.repr;
|
|
return json;
|
|
}
|
|
toSource() {
|
|
return this.repr;
|
|
}
|
|
};
|
|
var PercentageToken = class extends CSSParserToken {
|
|
tokenType = "PERCENTAGE";
|
|
repr;
|
|
constructor() {
|
|
super();
|
|
this.repr = "";
|
|
}
|
|
toString() {
|
|
return "PERCENTAGE(" + this.value + ")";
|
|
}
|
|
toJSON() {
|
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
json.value = this.value;
|
|
json.repr = this.repr;
|
|
return json;
|
|
}
|
|
toSource() {
|
|
return this.repr + "%";
|
|
}
|
|
};
|
|
var DimensionToken = class extends CSSParserToken {
|
|
tokenType = "DIMENSION";
|
|
type;
|
|
repr;
|
|
unit;
|
|
constructor() {
|
|
super();
|
|
this.type = "integer";
|
|
this.repr = "";
|
|
this.unit = "";
|
|
}
|
|
toString() {
|
|
return "DIM(" + this.value + "," + this.unit + ")";
|
|
}
|
|
toJSON() {
|
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
json.value = this.value;
|
|
json.type = this.type;
|
|
json.repr = this.repr;
|
|
json.unit = this.unit;
|
|
return json;
|
|
}
|
|
toSource() {
|
|
const source = this.repr;
|
|
let unit = escapeIdent(this.unit);
|
|
if (unit[0].toLowerCase() === "e" && (unit[1] === "-" || between(unit.charCodeAt(1), 48, 57))) {
|
|
unit = "\\65 " + unit.slice(1, unit.length);
|
|
}
|
|
return source + unit;
|
|
}
|
|
};
|
|
function escapeIdent(string) {
|
|
string = "" + string;
|
|
let result = "";
|
|
const firstcode = string.charCodeAt(0);
|
|
for (let i = 0; i < string.length; i++) {
|
|
const code = string.charCodeAt(i);
|
|
if (code === 0)
|
|
throw new InvalidCharacterError("Invalid character: the input contains U+0000.");
|
|
if (between(code, 1, 31) || code === 127 || i === 0 && between(code, 48, 57) || i === 1 && between(code, 48, 57) && firstcode === 45)
|
|
result += "\\" + code.toString(16) + " ";
|
|
else if (code >= 128 || code === 45 || code === 95 || between(code, 48, 57) || between(code, 65, 90) || between(code, 97, 122))
|
|
result += string[i];
|
|
else
|
|
result += "\\" + string[i];
|
|
}
|
|
return result;
|
|
}
|
|
function escapeHash(string) {
|
|
string = "" + string;
|
|
let result = "";
|
|
for (let i = 0; i < string.length; i++) {
|
|
const code = string.charCodeAt(i);
|
|
if (code === 0)
|
|
throw new InvalidCharacterError("Invalid character: the input contains U+0000.");
|
|
if (code >= 128 || code === 45 || code === 95 || between(code, 48, 57) || between(code, 65, 90) || between(code, 97, 122))
|
|
result += string[i];
|
|
else
|
|
result += "\\" + code.toString(16) + " ";
|
|
}
|
|
return result;
|
|
}
|
|
function escapeString(string) {
|
|
string = "" + string;
|
|
let result = "";
|
|
for (let i = 0; i < string.length; i++) {
|
|
const code = string.charCodeAt(i);
|
|
if (code === 0)
|
|
throw new InvalidCharacterError("Invalid character: the input contains U+0000.");
|
|
if (between(code, 1, 31) || code === 127)
|
|
result += "\\" + code.toString(16) + " ";
|
|
else if (code === 34 || code === 92)
|
|
result += "\\" + string[i];
|
|
else
|
|
result += string[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// packages/injected/src/roleUtils.ts
|
|
function hasExplicitAccessibleName(e) {
|
|
return e.hasAttribute("aria-label") || e.hasAttribute("aria-labelledby");
|
|
}
|
|
var kAncestorPreventingLandmark = "article:not([role]), aside:not([role]), main:not([role]), nav:not([role]), section:not([role]), [role=article], [role=complementary], [role=main], [role=navigation], [role=region]";
|
|
var kGlobalAriaAttributes = [
|
|
["aria-atomic", void 0],
|
|
["aria-busy", void 0],
|
|
["aria-controls", void 0],
|
|
["aria-current", void 0],
|
|
["aria-describedby", void 0],
|
|
["aria-details", void 0],
|
|
// Global use deprecated in ARIA 1.2
|
|
// ['aria-disabled', undefined],
|
|
["aria-dropeffect", void 0],
|
|
// Global use deprecated in ARIA 1.2
|
|
// ['aria-errormessage', undefined],
|
|
["aria-flowto", void 0],
|
|
["aria-grabbed", void 0],
|
|
// Global use deprecated in ARIA 1.2
|
|
// ['aria-haspopup', undefined],
|
|
["aria-hidden", void 0],
|
|
// Global use deprecated in ARIA 1.2
|
|
// ['aria-invalid', undefined],
|
|
["aria-keyshortcuts", void 0],
|
|
["aria-label", ["caption", "code", "deletion", "emphasis", "generic", "insertion", "paragraph", "presentation", "strong", "subscript", "superscript"]],
|
|
["aria-labelledby", ["caption", "code", "deletion", "emphasis", "generic", "insertion", "paragraph", "presentation", "strong", "subscript", "superscript"]],
|
|
["aria-live", void 0],
|
|
["aria-owns", void 0],
|
|
["aria-relevant", void 0],
|
|
["aria-roledescription", ["generic"]]
|
|
];
|
|
function hasGlobalAriaAttribute(element, forRole) {
|
|
return kGlobalAriaAttributes.some(([attr, prohibited]) => {
|
|
return !prohibited?.includes(forRole || "") && element.hasAttribute(attr);
|
|
});
|
|
}
|
|
function hasTabIndex(element) {
|
|
return !Number.isNaN(Number(String(element.getAttribute("tabindex"))));
|
|
}
|
|
function isFocusable(element) {
|
|
return !isNativelyDisabled(element) && (isNativelyFocusable(element) || hasTabIndex(element));
|
|
}
|
|
function isNativelyFocusable(element) {
|
|
const tagName = elementSafeTagName(element);
|
|
if (["BUTTON", "DETAILS", "SELECT", "TEXTAREA"].includes(tagName))
|
|
return true;
|
|
if (tagName === "A" || tagName === "AREA")
|
|
return element.hasAttribute("href");
|
|
if (tagName === "INPUT")
|
|
return !element.hidden;
|
|
return false;
|
|
}
|
|
var kImplicitRoleByTagName = {
|
|
"A": (e) => {
|
|
return e.hasAttribute("href") ? "link" : null;
|
|
},
|
|
"AREA": (e) => {
|
|
return e.hasAttribute("href") ? "link" : null;
|
|
},
|
|
"ARTICLE": () => "article",
|
|
"ASIDE": () => "complementary",
|
|
"BLOCKQUOTE": () => "blockquote",
|
|
"BUTTON": () => "button",
|
|
"CAPTION": () => "caption",
|
|
"CODE": () => "code",
|
|
"DATALIST": () => "listbox",
|
|
"DD": () => "definition",
|
|
"DEL": () => "deletion",
|
|
"DETAILS": () => "group",
|
|
"DFN": () => "term",
|
|
"DIALOG": () => "dialog",
|
|
"DT": () => "term",
|
|
"EM": () => "emphasis",
|
|
"FIELDSET": () => "group",
|
|
"FIGURE": () => "figure",
|
|
"FOOTER": (e) => closestCrossShadow(e, kAncestorPreventingLandmark) ? null : "contentinfo",
|
|
"FORM": (e) => hasExplicitAccessibleName(e) ? "form" : null,
|
|
"H1": () => "heading",
|
|
"H2": () => "heading",
|
|
"H3": () => "heading",
|
|
"H4": () => "heading",
|
|
"H5": () => "heading",
|
|
"H6": () => "heading",
|
|
"HEADER": (e) => closestCrossShadow(e, kAncestorPreventingLandmark) ? null : "banner",
|
|
"HR": () => "separator",
|
|
"HTML": () => "document",
|
|
"IMG": (e) => e.getAttribute("alt") === "" && !e.getAttribute("title") && !hasGlobalAriaAttribute(e) && !hasTabIndex(e) ? "presentation" : "img",
|
|
"INPUT": (e) => {
|
|
const type = e.type.toLowerCase();
|
|
if (type === "search")
|
|
return e.hasAttribute("list") ? "combobox" : "searchbox";
|
|
if (["email", "tel", "text", "url", ""].includes(type)) {
|
|
const list = getIdRefs(e, e.getAttribute("list"))[0];
|
|
return list && elementSafeTagName(list) === "DATALIST" ? "combobox" : "textbox";
|
|
}
|
|
if (type === "hidden")
|
|
return null;
|
|
if (type === "file")
|
|
return "button";
|
|
return inputTypeToRole[type] || "textbox";
|
|
},
|
|
"INS": () => "insertion",
|
|
"LI": () => "listitem",
|
|
"MAIN": () => "main",
|
|
"MARK": () => "mark",
|
|
"MATH": () => "math",
|
|
"MENU": () => "list",
|
|
"METER": () => "meter",
|
|
"NAV": () => "navigation",
|
|
"OL": () => "list",
|
|
"OPTGROUP": () => "group",
|
|
"OPTION": () => "option",
|
|
"OUTPUT": () => "status",
|
|
"P": () => "paragraph",
|
|
"PROGRESS": () => "progressbar",
|
|
"SEARCH": () => "search",
|
|
"SECTION": (e) => hasExplicitAccessibleName(e) ? "region" : null,
|
|
"SELECT": (e) => e.hasAttribute("multiple") || e.size > 1 ? "listbox" : "combobox",
|
|
"STRONG": () => "strong",
|
|
"SUB": () => "subscript",
|
|
"SUP": () => "superscript",
|
|
// For <svg> we default to Chrome behavior:
|
|
// - Chrome reports 'img'.
|
|
// - Firefox reports 'diagram' that is not in official ARIA spec yet.
|
|
// - Safari reports 'no role', but still computes accessible name.
|
|
"SVG": () => "img",
|
|
"TABLE": () => "table",
|
|
"TBODY": () => "rowgroup",
|
|
"TD": (e) => {
|
|
const table = closestCrossShadow(e, "table");
|
|
const role = table ? getExplicitAriaRole(table) : "";
|
|
return role === "grid" || role === "treegrid" ? "gridcell" : "cell";
|
|
},
|
|
"TEXTAREA": () => "textbox",
|
|
"TFOOT": () => "rowgroup",
|
|
"TH": (e) => {
|
|
const scope = e.getAttribute("scope");
|
|
if (scope === "col" || scope === "colgroup")
|
|
return "columnheader";
|
|
if (scope === "row" || scope === "rowgroup")
|
|
return "rowheader";
|
|
const nextSibling = e.nextElementSibling;
|
|
const prevSibling = e.previousElementSibling;
|
|
const row = !!e.parentElement && elementSafeTagName(e.parentElement) === "TR" ? e.parentElement : void 0;
|
|
if (!nextSibling && !prevSibling) {
|
|
if (row) {
|
|
const table = closestCrossShadow(row, "table");
|
|
if (table && table.rows.length <= 1)
|
|
return null;
|
|
}
|
|
return "columnheader";
|
|
}
|
|
if (isHeaderCell(nextSibling) && isHeaderCell(prevSibling))
|
|
return "columnheader";
|
|
if (isNonEmptyDataCell(nextSibling) || isNonEmptyDataCell(prevSibling))
|
|
return "rowheader";
|
|
return "columnheader";
|
|
},
|
|
"THEAD": () => "rowgroup",
|
|
"TIME": () => "time",
|
|
"TR": () => "row",
|
|
"UL": () => "list"
|
|
};
|
|
function isHeaderCell(element) {
|
|
return !!element && elementSafeTagName(element) === "TH";
|
|
}
|
|
function isNonEmptyDataCell(element) {
|
|
if (!element || elementSafeTagName(element) !== "TD")
|
|
return false;
|
|
return !!(element.textContent?.trim() || element.children.length > 0);
|
|
}
|
|
var kPresentationInheritanceParents = {
|
|
"DD": ["DL", "DIV"],
|
|
"DIV": ["DL"],
|
|
"DT": ["DL", "DIV"],
|
|
"LI": ["OL", "UL"],
|
|
"TBODY": ["TABLE"],
|
|
"TD": ["TR"],
|
|
"TFOOT": ["TABLE"],
|
|
"TH": ["TR"],
|
|
"THEAD": ["TABLE"],
|
|
"TR": ["THEAD", "TBODY", "TFOOT", "TABLE"]
|
|
};
|
|
function getImplicitAriaRole(element) {
|
|
const implicitRole = kImplicitRoleByTagName[elementSafeTagName(element)]?.(element) || "";
|
|
if (!implicitRole)
|
|
return null;
|
|
let ancestor = element;
|
|
while (ancestor) {
|
|
const parent = parentElementOrShadowHost(ancestor);
|
|
const parents = kPresentationInheritanceParents[elementSafeTagName(ancestor)];
|
|
if (!parents || !parent || !parents.includes(elementSafeTagName(parent)))
|
|
break;
|
|
const parentExplicitRole = getExplicitAriaRole(parent);
|
|
if ((parentExplicitRole === "none" || parentExplicitRole === "presentation") && !hasPresentationConflictResolution(parent, parentExplicitRole))
|
|
return parentExplicitRole;
|
|
ancestor = parent;
|
|
}
|
|
return implicitRole;
|
|
}
|
|
var validRoles = [
|
|
"alert",
|
|
"alertdialog",
|
|
"application",
|
|
"article",
|
|
"banner",
|
|
"blockquote",
|
|
"button",
|
|
"caption",
|
|
"cell",
|
|
"checkbox",
|
|
"code",
|
|
"columnheader",
|
|
"combobox",
|
|
"complementary",
|
|
"contentinfo",
|
|
"definition",
|
|
"deletion",
|
|
"dialog",
|
|
"directory",
|
|
"document",
|
|
"emphasis",
|
|
"feed",
|
|
"figure",
|
|
"form",
|
|
"generic",
|
|
"grid",
|
|
"gridcell",
|
|
"group",
|
|
"heading",
|
|
"img",
|
|
"insertion",
|
|
"link",
|
|
"list",
|
|
"listbox",
|
|
"listitem",
|
|
"log",
|
|
"main",
|
|
"mark",
|
|
"marquee",
|
|
"math",
|
|
"meter",
|
|
"menu",
|
|
"menubar",
|
|
"menuitem",
|
|
"menuitemcheckbox",
|
|
"menuitemradio",
|
|
"navigation",
|
|
"none",
|
|
"note",
|
|
"option",
|
|
"paragraph",
|
|
"presentation",
|
|
"progressbar",
|
|
"radio",
|
|
"radiogroup",
|
|
"region",
|
|
"row",
|
|
"rowgroup",
|
|
"rowheader",
|
|
"scrollbar",
|
|
"search",
|
|
"searchbox",
|
|
"separator",
|
|
"slider",
|
|
"spinbutton",
|
|
"status",
|
|
"strong",
|
|
"subscript",
|
|
"superscript",
|
|
"switch",
|
|
"tab",
|
|
"table",
|
|
"tablist",
|
|
"tabpanel",
|
|
"term",
|
|
"textbox",
|
|
"time",
|
|
"timer",
|
|
"toolbar",
|
|
"tooltip",
|
|
"tree",
|
|
"treegrid",
|
|
"treeitem"
|
|
];
|
|
function getExplicitAriaRole(element) {
|
|
const roles = (element.getAttribute("role") || "").split(" ").map((role) => role.trim());
|
|
return roles.find((role) => validRoles.includes(role)) || null;
|
|
}
|
|
function hasPresentationConflictResolution(element, role) {
|
|
return hasGlobalAriaAttribute(element, role) || isFocusable(element);
|
|
}
|
|
function getAriaRole(element) {
|
|
const explicitRole = getExplicitAriaRole(element);
|
|
if (!explicitRole)
|
|
return getImplicitAriaRole(element);
|
|
if (explicitRole === "none" || explicitRole === "presentation") {
|
|
const implicitRole = getImplicitAriaRole(element);
|
|
if (hasPresentationConflictResolution(element, implicitRole))
|
|
return implicitRole;
|
|
}
|
|
return explicitRole;
|
|
}
|
|
function getAriaBoolean(attr) {
|
|
return attr === null ? void 0 : attr.toLowerCase() === "true";
|
|
}
|
|
function isElementIgnoredForAria(element) {
|
|
return ["STYLE", "SCRIPT", "NOSCRIPT", "TEMPLATE"].includes(elementSafeTagName(element));
|
|
}
|
|
function isElementHiddenForAria(element) {
|
|
if (isElementIgnoredForAria(element))
|
|
return true;
|
|
const style = getElementComputedStyle(element);
|
|
const isSlot = element.nodeName === "SLOT";
|
|
if (style?.display === "contents" && !isSlot) {
|
|
for (let child = element.firstChild; child; child = child.nextSibling) {
|
|
if (child.nodeType === 1 && !isElementHiddenForAria(child))
|
|
return false;
|
|
if (child.nodeType === 3 && isVisibleTextNode(child))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
const isOptionInsideSelect = element.nodeName === "OPTION" && !!element.closest("select");
|
|
if (!isOptionInsideSelect && !isSlot && !isElementStyleVisibilityVisible(element, style))
|
|
return true;
|
|
return belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element);
|
|
}
|
|
function belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element) {
|
|
let hidden = cacheIsHidden?.get(element);
|
|
if (hidden === void 0) {
|
|
hidden = false;
|
|
if (element.parentElement && element.parentElement.shadowRoot && !element.assignedSlot)
|
|
hidden = true;
|
|
if (!hidden) {
|
|
const style = getElementComputedStyle(element);
|
|
hidden = !style || style.display === "none" || getAriaBoolean(element.getAttribute("aria-hidden")) === true;
|
|
}
|
|
if (!hidden) {
|
|
const parent = parentElementOrShadowHost(element);
|
|
if (parent)
|
|
hidden = belongsToDisplayNoneOrAriaHiddenOrNonSlotted(parent);
|
|
}
|
|
cacheIsHidden?.set(element, hidden);
|
|
}
|
|
return hidden;
|
|
}
|
|
function getIdRefs(element, ref) {
|
|
if (!ref)
|
|
return [];
|
|
const root = enclosingShadowRootOrDocument(element);
|
|
if (!root)
|
|
return [];
|
|
try {
|
|
const ids = ref.split(" ").filter((id) => !!id);
|
|
const result = [];
|
|
for (const id of ids) {
|
|
const firstElement = root.querySelector("#" + CSS.escape(id));
|
|
if (firstElement && !result.includes(firstElement))
|
|
result.push(firstElement);
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
function trimFlatString(s) {
|
|
return s.trim();
|
|
}
|
|
function asFlatString(s) {
|
|
return s.split("\xA0").map((chunk) => chunk.replace(/\r\n/g, "\n").replace(/[\u200b\u00ad]/g, "").replace(/\s\s*/g, " ")).join("\xA0").trim();
|
|
}
|
|
function queryInAriaOwned(element, selector) {
|
|
const result = [...element.querySelectorAll(selector)];
|
|
for (const owned of getIdRefs(element, element.getAttribute("aria-owns"))) {
|
|
if (owned.matches(selector))
|
|
result.push(owned);
|
|
result.push(...owned.querySelectorAll(selector));
|
|
}
|
|
return result;
|
|
}
|
|
function getCSSContent(element, pseudo) {
|
|
const cache = pseudo === "::before" ? cachePseudoContentBefore : pseudo === "::after" ? cachePseudoContentAfter : cachePseudoContent;
|
|
if (cache?.has(element))
|
|
return cache?.get(element);
|
|
const style = getElementComputedStyle(element, pseudo);
|
|
let content;
|
|
if (style) {
|
|
const contentValue = style.content;
|
|
if (contentValue && contentValue !== "none" && contentValue !== "normal") {
|
|
if (style.display !== "none" && style.visibility !== "hidden") {
|
|
content = parseCSSContentPropertyAsString(element, contentValue, !!pseudo);
|
|
}
|
|
}
|
|
}
|
|
if (pseudo && content !== void 0) {
|
|
const display = style?.display || "inline";
|
|
if (display !== "inline")
|
|
content = " " + content + " ";
|
|
}
|
|
if (cache)
|
|
cache.set(element, content);
|
|
return content;
|
|
}
|
|
function parseCSSContentPropertyAsString(element, content, isPseudo) {
|
|
if (!content || content === "none" || content === "normal") {
|
|
return;
|
|
}
|
|
try {
|
|
let tokens = tokenize(content).filter((token) => !(token instanceof WhitespaceToken));
|
|
const delimIndex = tokens.findIndex((token) => token instanceof DelimToken && token.value === "/");
|
|
if (delimIndex !== -1) {
|
|
tokens = tokens.slice(delimIndex + 1);
|
|
} else if (!isPseudo) {
|
|
return;
|
|
}
|
|
const accumulated = [];
|
|
let index = 0;
|
|
while (index < tokens.length) {
|
|
if (tokens[index] instanceof StringToken) {
|
|
accumulated.push(tokens[index].value);
|
|
index++;
|
|
} else if (index + 2 < tokens.length && tokens[index] instanceof FunctionToken && tokens[index].value === "attr" && tokens[index + 1] instanceof IdentToken && tokens[index + 2] instanceof CloseParenToken) {
|
|
const attrName = tokens[index + 1].value;
|
|
accumulated.push(element.getAttribute(attrName) || "");
|
|
index += 3;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
return accumulated.join("");
|
|
} catch {
|
|
}
|
|
}
|
|
function getAriaLabelledByElements(element) {
|
|
const ref = element.getAttribute("aria-labelledby");
|
|
if (ref === null)
|
|
return null;
|
|
const refs = getIdRefs(element, ref);
|
|
return refs.length ? refs : null;
|
|
}
|
|
function allowsNameFromContent(role, targetDescendant) {
|
|
const alwaysAllowsNameFromContent = ["button", "cell", "checkbox", "columnheader", "gridcell", "heading", "link", "menuitem", "menuitemcheckbox", "menuitemradio", "option", "radio", "row", "rowheader", "switch", "tab", "tooltip", "treeitem"].includes(role);
|
|
const descendantAllowsNameFromContent = targetDescendant && ["", "caption", "code", "contentinfo", "definition", "deletion", "emphasis", "insertion", "list", "listitem", "mark", "none", "paragraph", "presentation", "region", "row", "rowgroup", "section", "strong", "subscript", "superscript", "table", "term", "time"].includes(role);
|
|
return alwaysAllowsNameFromContent || descendantAllowsNameFromContent;
|
|
}
|
|
function getElementAccessibleName(element, includeHidden) {
|
|
const cache = includeHidden ? cacheAccessibleNameHidden : cacheAccessibleName;
|
|
let accessibleName = cache?.get(element);
|
|
if (accessibleName === void 0) {
|
|
accessibleName = "";
|
|
const elementProhibitsNaming = ["caption", "code", "definition", "deletion", "emphasis", "generic", "insertion", "mark", "paragraph", "presentation", "strong", "subscript", "suggestion", "superscript", "term", "time"].includes(getAriaRole(element) || "");
|
|
if (!elementProhibitsNaming) {
|
|
accessibleName = asFlatString(getTextAlternativeInternal(element, {
|
|
includeHidden,
|
|
visitedElements: /* @__PURE__ */ new Set(),
|
|
embeddedInTargetElement: "self"
|
|
}));
|
|
}
|
|
cache?.set(element, accessibleName);
|
|
}
|
|
return accessibleName;
|
|
}
|
|
var kAriaInvalidRoles = [
|
|
"application",
|
|
"checkbox",
|
|
"columnheader",
|
|
"combobox",
|
|
"gridcell",
|
|
"listbox",
|
|
"radiogroup",
|
|
"rowheader",
|
|
"searchbox",
|
|
"slider",
|
|
"spinbutton",
|
|
"switch",
|
|
"textbox",
|
|
"tree"
|
|
];
|
|
function getAriaInvalid(element) {
|
|
const ariaInvalid = element.getAttribute("aria-invalid");
|
|
if (!ariaInvalid || ariaInvalid.trim() === "" || ariaInvalid.toLocaleLowerCase() === "false")
|
|
return "false";
|
|
if (ariaInvalid === "true" || ariaInvalid === "grammar" || ariaInvalid === "spelling")
|
|
return ariaInvalid;
|
|
return "true";
|
|
}
|
|
function getTextAlternativeInternal(element, options) {
|
|
if (options.visitedElements.has(element))
|
|
return "";
|
|
const childOptions = {
|
|
...options,
|
|
embeddedInTargetElement: options.embeddedInTargetElement === "self" ? "descendant" : options.embeddedInTargetElement
|
|
};
|
|
if (!options.includeHidden) {
|
|
const isEmbeddedInHiddenReferenceTraversal = !!options.embeddedInLabelledBy?.hidden || !!options.embeddedInDescribedBy?.hidden || !!options.embeddedInNativeTextAlternative?.hidden || !!options.embeddedInLabel?.hidden;
|
|
if (isElementIgnoredForAria(element) || !isEmbeddedInHiddenReferenceTraversal && isElementHiddenForAria(element)) {
|
|
options.visitedElements.add(element);
|
|
return "";
|
|
}
|
|
}
|
|
const labelledBy = getAriaLabelledByElements(element);
|
|
if (!options.embeddedInLabelledBy) {
|
|
const accessibleName = (labelledBy || []).map((ref) => getTextAlternativeInternal(ref, {
|
|
...options,
|
|
embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) },
|
|
embeddedInDescribedBy: void 0,
|
|
embeddedInTargetElement: void 0,
|
|
embeddedInLabel: void 0,
|
|
embeddedInNativeTextAlternative: void 0
|
|
})).join(" ");
|
|
if (accessibleName)
|
|
return accessibleName;
|
|
}
|
|
const role = getAriaRole(element) || "";
|
|
const tagName = elementSafeTagName(element);
|
|
if (!!options.embeddedInLabel || !!options.embeddedInLabelledBy || options.embeddedInTargetElement === "descendant") {
|
|
const isOwnLabel = [...element.labels || []].includes(element);
|
|
const isOwnLabelledBy = (labelledBy || []).includes(element);
|
|
if (!isOwnLabel && !isOwnLabelledBy) {
|
|
if (role === "textbox") {
|
|
options.visitedElements.add(element);
|
|
if (tagName === "INPUT" || tagName === "TEXTAREA")
|
|
return element.value;
|
|
return element.textContent || "";
|
|
}
|
|
if (["combobox", "listbox"].includes(role)) {
|
|
options.visitedElements.add(element);
|
|
let selectedOptions;
|
|
if (tagName === "SELECT") {
|
|
selectedOptions = [...element.selectedOptions];
|
|
if (!selectedOptions.length && element.options.length)
|
|
selectedOptions.push(element.options[0]);
|
|
} else {
|
|
const listbox = role === "combobox" ? queryInAriaOwned(element, "*").find((e) => getAriaRole(e) === "listbox") : element;
|
|
selectedOptions = listbox ? queryInAriaOwned(listbox, '[aria-selected="true"]').filter((e) => getAriaRole(e) === "option") : [];
|
|
}
|
|
if (!selectedOptions.length && tagName === "INPUT") {
|
|
return element.value;
|
|
}
|
|
return selectedOptions.map((option) => getTextAlternativeInternal(option, childOptions)).join(" ");
|
|
}
|
|
if (["progressbar", "scrollbar", "slider", "spinbutton", "meter"].includes(role)) {
|
|
options.visitedElements.add(element);
|
|
if (element.hasAttribute("aria-valuetext"))
|
|
return element.getAttribute("aria-valuetext") || "";
|
|
if (element.hasAttribute("aria-valuenow"))
|
|
return element.getAttribute("aria-valuenow") || "";
|
|
return element.getAttribute("value") || "";
|
|
}
|
|
if (["menu"].includes(role)) {
|
|
options.visitedElements.add(element);
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
const ariaLabel = element.getAttribute("aria-label") || "";
|
|
if (trimFlatString(ariaLabel)) {
|
|
options.visitedElements.add(element);
|
|
return ariaLabel;
|
|
}
|
|
if (!["presentation", "none"].includes(role)) {
|
|
if (tagName === "INPUT" && ["button", "submit", "reset"].includes(element.type)) {
|
|
options.visitedElements.add(element);
|
|
const value = element.value || "";
|
|
if (trimFlatString(value))
|
|
return value;
|
|
if (element.type === "submit")
|
|
return "Submit";
|
|
if (element.type === "reset")
|
|
return "Reset";
|
|
const title = element.getAttribute("title") || "";
|
|
return title;
|
|
}
|
|
if (tagName === "INPUT" && element.type === "file") {
|
|
options.visitedElements.add(element);
|
|
const labels = element.labels || [];
|
|
if (labels.length && !options.embeddedInLabelledBy)
|
|
return getAccessibleNameFromAssociatedLabels(labels, options);
|
|
return "Choose File";
|
|
}
|
|
if (tagName === "INPUT" && element.type === "image") {
|
|
options.visitedElements.add(element);
|
|
const labels = element.labels || [];
|
|
if (labels.length && !options.embeddedInLabelledBy)
|
|
return getAccessibleNameFromAssociatedLabels(labels, options);
|
|
const alt = element.getAttribute("alt") || "";
|
|
if (trimFlatString(alt))
|
|
return alt;
|
|
const title = element.getAttribute("title") || "";
|
|
if (trimFlatString(title))
|
|
return title;
|
|
return "Submit";
|
|
}
|
|
if (!labelledBy && tagName === "BUTTON") {
|
|
options.visitedElements.add(element);
|
|
const labels = element.labels || [];
|
|
if (labels.length)
|
|
return getAccessibleNameFromAssociatedLabels(labels, options);
|
|
}
|
|
if (!labelledBy && tagName === "OUTPUT") {
|
|
options.visitedElements.add(element);
|
|
const labels = element.labels || [];
|
|
if (labels.length)
|
|
return getAccessibleNameFromAssociatedLabels(labels, options);
|
|
return element.getAttribute("title") || "";
|
|
}
|
|
if (!labelledBy && (tagName === "TEXTAREA" || tagName === "SELECT" || tagName === "INPUT")) {
|
|
options.visitedElements.add(element);
|
|
const labels = element.labels || [];
|
|
if (labels.length)
|
|
return getAccessibleNameFromAssociatedLabels(labels, options);
|
|
const usePlaceholder = tagName === "INPUT" && ["text", "password", "search", "tel", "email", "url"].includes(element.type) || tagName === "TEXTAREA";
|
|
const placeholder = element.getAttribute("placeholder") || "";
|
|
const title = element.getAttribute("title") || "";
|
|
if (!usePlaceholder || title)
|
|
return title;
|
|
return placeholder;
|
|
}
|
|
if (!labelledBy && tagName === "FIELDSET") {
|
|
options.visitedElements.add(element);
|
|
for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
|
|
if (elementSafeTagName(child) === "LEGEND") {
|
|
return getTextAlternativeInternal(child, {
|
|
...childOptions,
|
|
embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) }
|
|
});
|
|
}
|
|
}
|
|
const title = element.getAttribute("title") || "";
|
|
return title;
|
|
}
|
|
if (!labelledBy && tagName === "FIGURE") {
|
|
options.visitedElements.add(element);
|
|
for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
|
|
if (elementSafeTagName(child) === "FIGCAPTION") {
|
|
return getTextAlternativeInternal(child, {
|
|
...childOptions,
|
|
embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) }
|
|
});
|
|
}
|
|
}
|
|
const title = element.getAttribute("title") || "";
|
|
return title;
|
|
}
|
|
if (tagName === "IMG") {
|
|
options.visitedElements.add(element);
|
|
const alt = element.getAttribute("alt") || "";
|
|
if (trimFlatString(alt))
|
|
return alt;
|
|
const title = element.getAttribute("title") || "";
|
|
return title;
|
|
}
|
|
if (tagName === "TABLE") {
|
|
options.visitedElements.add(element);
|
|
for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
|
|
if (elementSafeTagName(child) === "CAPTION") {
|
|
return getTextAlternativeInternal(child, {
|
|
...childOptions,
|
|
embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) }
|
|
});
|
|
}
|
|
}
|
|
const summary = element.getAttribute("summary") || "";
|
|
if (summary)
|
|
return summary;
|
|
}
|
|
if (tagName === "AREA") {
|
|
options.visitedElements.add(element);
|
|
const alt = element.getAttribute("alt") || "";
|
|
if (trimFlatString(alt))
|
|
return alt;
|
|
const title = element.getAttribute("title") || "";
|
|
return title;
|
|
}
|
|
if (tagName === "SVG" || element.ownerSVGElement) {
|
|
options.visitedElements.add(element);
|
|
for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
|
|
if (elementSafeTagName(child) === "TITLE" && child.ownerSVGElement) {
|
|
return getTextAlternativeInternal(child, {
|
|
...childOptions,
|
|
embeddedInLabelledBy: { element: child, hidden: isElementHiddenForAria(child) }
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (element.ownerSVGElement && tagName === "A") {
|
|
const title = element.getAttribute("xlink:title") || "";
|
|
if (trimFlatString(title)) {
|
|
options.visitedElements.add(element);
|
|
return title;
|
|
}
|
|
}
|
|
}
|
|
const shouldNameFromContentForSummary = tagName === "SUMMARY" && !["presentation", "none"].includes(role);
|
|
if (allowsNameFromContent(role, options.embeddedInTargetElement === "descendant") || shouldNameFromContentForSummary || !!options.embeddedInLabelledBy || !!options.embeddedInDescribedBy || !!options.embeddedInLabel || !!options.embeddedInNativeTextAlternative) {
|
|
options.visitedElements.add(element);
|
|
const accessibleName = innerAccumulatedElementText(element, childOptions);
|
|
const maybeTrimmedAccessibleName = options.embeddedInTargetElement === "self" ? trimFlatString(accessibleName) : accessibleName;
|
|
if (maybeTrimmedAccessibleName)
|
|
return accessibleName;
|
|
}
|
|
if (!["presentation", "none"].includes(role) || tagName === "IFRAME") {
|
|
options.visitedElements.add(element);
|
|
const title = element.getAttribute("title") || "";
|
|
if (trimFlatString(title))
|
|
return title;
|
|
}
|
|
options.visitedElements.add(element);
|
|
return "";
|
|
}
|
|
function innerAccumulatedElementText(element, options) {
|
|
const tokens = [];
|
|
const visit = (node, skipSlotted) => {
|
|
if (skipSlotted && node.assignedSlot)
|
|
return;
|
|
if (node.nodeType === 1) {
|
|
const display = getElementComputedStyle(node)?.display || "inline";
|
|
let token = getTextAlternativeInternal(node, options);
|
|
if (display !== "inline" || node.nodeName === "BR")
|
|
token = " " + token + " ";
|
|
tokens.push(token);
|
|
} else if (node.nodeType === 3) {
|
|
tokens.push(node.textContent || "");
|
|
}
|
|
};
|
|
tokens.push(getCSSContent(element, "::before") || "");
|
|
const content = getCSSContent(element);
|
|
if (content !== void 0) {
|
|
tokens.push(content);
|
|
} else {
|
|
const assignedNodes = element.nodeName === "SLOT" ? element.assignedNodes() : [];
|
|
if (assignedNodes.length) {
|
|
for (const child of assignedNodes)
|
|
visit(child, false);
|
|
} else {
|
|
for (let child = element.firstChild; child; child = child.nextSibling)
|
|
visit(child, true);
|
|
if (element.shadowRoot) {
|
|
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
|
|
visit(child, true);
|
|
}
|
|
for (const owned of getIdRefs(element, element.getAttribute("aria-owns")))
|
|
visit(owned, true);
|
|
}
|
|
}
|
|
tokens.push(getCSSContent(element, "::after") || "");
|
|
return tokens.join("");
|
|
}
|
|
var kAriaSelectedRoles = ["gridcell", "option", "row", "tab", "rowheader", "columnheader", "treeitem"];
|
|
function getAriaSelected(element) {
|
|
if (elementSafeTagName(element) === "OPTION")
|
|
return element.selected;
|
|
if (kAriaSelectedRoles.includes(getAriaRole(element) || ""))
|
|
return getAriaBoolean(element.getAttribute("aria-selected")) === true;
|
|
return false;
|
|
}
|
|
var kAriaCheckedRoles = ["checkbox", "menuitemcheckbox", "option", "radio", "switch", "menuitemradio", "treeitem"];
|
|
function getAriaChecked(element) {
|
|
const result = getChecked(element, true);
|
|
return result === "error" ? false : result;
|
|
}
|
|
function getChecked(element, allowMixed) {
|
|
const tagName = elementSafeTagName(element);
|
|
if (allowMixed && tagName === "INPUT" && element.indeterminate)
|
|
return "mixed";
|
|
if (tagName === "INPUT" && ["checkbox", "radio"].includes(element.type))
|
|
return element.checked;
|
|
if (kAriaCheckedRoles.includes(getAriaRole(element) || "")) {
|
|
const checked = element.getAttribute("aria-checked");
|
|
if (checked === "true")
|
|
return true;
|
|
if (allowMixed && checked === "mixed")
|
|
return "mixed";
|
|
return false;
|
|
}
|
|
return "error";
|
|
}
|
|
var kAriaPressedRoles = ["button"];
|
|
function getAriaPressed(element) {
|
|
if (kAriaPressedRoles.includes(getAriaRole(element) || "")) {
|
|
const pressed = element.getAttribute("aria-pressed");
|
|
if (pressed === "true")
|
|
return true;
|
|
if (pressed === "mixed")
|
|
return "mixed";
|
|
}
|
|
return false;
|
|
}
|
|
var kAriaExpandedRoles = ["application", "button", "checkbox", "combobox", "gridcell", "link", "listbox", "menuitem", "row", "rowheader", "tab", "treeitem", "columnheader", "menuitemcheckbox", "menuitemradio", "rowheader", "switch"];
|
|
function getAriaExpanded(element) {
|
|
if (elementSafeTagName(element) === "DETAILS")
|
|
return element.open;
|
|
if (kAriaExpandedRoles.includes(getAriaRole(element) || "")) {
|
|
const expanded = element.getAttribute("aria-expanded");
|
|
if (expanded === null)
|
|
return void 0;
|
|
if (expanded === "true")
|
|
return true;
|
|
return false;
|
|
}
|
|
return void 0;
|
|
}
|
|
var kAriaLevelRoles = ["heading", "listitem", "row", "treeitem"];
|
|
function getAriaLevel(element) {
|
|
const native = { "H1": 1, "H2": 2, "H3": 3, "H4": 4, "H5": 5, "H6": 6 }[elementSafeTagName(element)];
|
|
if (native)
|
|
return native;
|
|
if (kAriaLevelRoles.includes(getAriaRole(element) || "")) {
|
|
const attr = element.getAttribute("aria-level");
|
|
const value = attr === null ? Number.NaN : Number(attr);
|
|
if (Number.isInteger(value) && value >= 1)
|
|
return value;
|
|
}
|
|
return 0;
|
|
}
|
|
var kAriaDisabledRoles = ["application", "button", "composite", "gridcell", "group", "input", "link", "menuitem", "scrollbar", "separator", "tab", "checkbox", "columnheader", "combobox", "grid", "listbox", "menu", "menubar", "menuitemcheckbox", "menuitemradio", "option", "radio", "radiogroup", "row", "rowheader", "searchbox", "select", "slider", "spinbutton", "switch", "tablist", "textbox", "toolbar", "tree", "treegrid", "treeitem"];
|
|
function getAriaDisabled(element) {
|
|
return isNativelyDisabled(element) || hasExplicitAriaDisabled(element);
|
|
}
|
|
function isNativelyDisabled(element) {
|
|
const isNativeFormControl = ["BUTTON", "INPUT", "SELECT", "TEXTAREA", "OPTION", "OPTGROUP"].includes(elementSafeTagName(element));
|
|
return isNativeFormControl && (element.hasAttribute("disabled") || belongsToDisabledOptGroup(element) || belongsToDisabledFieldSet(element));
|
|
}
|
|
function belongsToDisabledOptGroup(element) {
|
|
return elementSafeTagName(element) === "OPTION" && !!element.closest("OPTGROUP[DISABLED]");
|
|
}
|
|
function belongsToDisabledFieldSet(element) {
|
|
const fieldSetElement = element?.closest("FIELDSET[DISABLED]");
|
|
if (!fieldSetElement)
|
|
return false;
|
|
const legendElement = fieldSetElement.querySelector(":scope > LEGEND");
|
|
return !legendElement || !legendElement.contains(element);
|
|
}
|
|
function hasExplicitAriaDisabled(element, isAncestor = false) {
|
|
if (!element)
|
|
return false;
|
|
if (isAncestor || kAriaDisabledRoles.includes(getAriaRole(element) || "")) {
|
|
const attribute = (element.getAttribute("aria-disabled") || "").toLowerCase();
|
|
if (attribute === "true")
|
|
return true;
|
|
if (attribute === "false")
|
|
return false;
|
|
return hasExplicitAriaDisabled(parentElementOrShadowHost(element), true);
|
|
}
|
|
return false;
|
|
}
|
|
function getAccessibleNameFromAssociatedLabels(labels, options) {
|
|
return [...labels].map((label) => getTextAlternativeInternal(label, {
|
|
...options,
|
|
embeddedInLabel: { element: label, hidden: isElementHiddenForAria(label) },
|
|
embeddedInNativeTextAlternative: void 0,
|
|
embeddedInLabelledBy: void 0,
|
|
embeddedInDescribedBy: void 0,
|
|
embeddedInTargetElement: void 0
|
|
})).filter((accessibleName) => !!accessibleName).join(" ");
|
|
}
|
|
function receivesPointerEvents(element) {
|
|
const cache = cachePointerEvents;
|
|
let e = element;
|
|
let result;
|
|
const parents = [];
|
|
for (; e; e = parentElementOrShadowHost(e)) {
|
|
const cached = cache.get(e);
|
|
if (cached !== void 0) {
|
|
result = cached;
|
|
break;
|
|
}
|
|
parents.push(e);
|
|
const style = getElementComputedStyle(e);
|
|
if (!style) {
|
|
result = true;
|
|
break;
|
|
}
|
|
const value = style.pointerEvents;
|
|
if (value) {
|
|
result = value !== "none";
|
|
break;
|
|
}
|
|
}
|
|
if (result === void 0)
|
|
result = true;
|
|
for (const parent of parents)
|
|
cache.set(parent, result);
|
|
return result;
|
|
}
|
|
var cacheAccessibleName;
|
|
var cacheAccessibleNameHidden;
|
|
var cacheAccessibleDescription;
|
|
var cacheAccessibleDescriptionHidden;
|
|
var cacheAccessibleErrorMessage;
|
|
var cacheIsHidden;
|
|
var cachePseudoContent;
|
|
var cachePseudoContentBefore;
|
|
var cachePseudoContentAfter;
|
|
var cachePointerEvents;
|
|
var cachesCounter2 = 0;
|
|
function beginAriaCaches() {
|
|
beginDOMCaches();
|
|
++cachesCounter2;
|
|
cacheAccessibleName ??= /* @__PURE__ */ new Map();
|
|
cacheAccessibleNameHidden ??= /* @__PURE__ */ new Map();
|
|
cacheAccessibleDescription ??= /* @__PURE__ */ new Map();
|
|
cacheAccessibleDescriptionHidden ??= /* @__PURE__ */ new Map();
|
|
cacheAccessibleErrorMessage ??= /* @__PURE__ */ new Map();
|
|
cacheIsHidden ??= /* @__PURE__ */ new Map();
|
|
cachePseudoContent ??= /* @__PURE__ */ new Map();
|
|
cachePseudoContentBefore ??= /* @__PURE__ */ new Map();
|
|
cachePseudoContentAfter ??= /* @__PURE__ */ new Map();
|
|
cachePointerEvents ??= /* @__PURE__ */ new Map();
|
|
}
|
|
function endAriaCaches() {
|
|
if (!--cachesCounter2) {
|
|
cacheAccessibleName = void 0;
|
|
cacheAccessibleNameHidden = void 0;
|
|
cacheAccessibleDescription = void 0;
|
|
cacheAccessibleDescriptionHidden = void 0;
|
|
cacheAccessibleErrorMessage = void 0;
|
|
cacheIsHidden = void 0;
|
|
cachePseudoContent = void 0;
|
|
cachePseudoContentBefore = void 0;
|
|
cachePseudoContentAfter = void 0;
|
|
cachePointerEvents = void 0;
|
|
}
|
|
endDOMCaches();
|
|
}
|
|
var inputTypeToRole = {
|
|
"button": "button",
|
|
"checkbox": "checkbox",
|
|
"image": "button",
|
|
"number": "spinbutton",
|
|
"radio": "radio",
|
|
"range": "slider",
|
|
"reset": "button",
|
|
"submit": "button"
|
|
};
|
|
|
|
// packages/injected/src/ariaSnapshot.ts
|
|
var lastRef = 0;
|
|
function toInternalOptions(options) {
|
|
const renderBoxes = options.boxes;
|
|
if (options.mode === "ai") {
|
|
return {
|
|
visibility: "ariaOrVisible",
|
|
refs: "interactable",
|
|
refPrefix: options.refPrefix,
|
|
includeGenericRole: true,
|
|
renderActive: !options.doNotRenderActive,
|
|
renderCursorPointer: true,
|
|
renderBoxes
|
|
};
|
|
}
|
|
if (options.mode === "autoexpect") {
|
|
return { visibility: "ariaAndVisible", refs: "none", renderBoxes };
|
|
}
|
|
if (options.mode === "codegen") {
|
|
return { visibility: "aria", refs: "none", renderStringsAsRegex: true, renderBoxes };
|
|
}
|
|
return { visibility: "aria", refs: "none", renderBoxes };
|
|
}
|
|
function generateAriaTree(rootElement, publicOptions) {
|
|
const options = toInternalOptions(publicOptions);
|
|
const visited = /* @__PURE__ */ new Set();
|
|
const snapshot = {
|
|
root: { role: "fragment", name: "", children: [], props: {}, box: computeBox(rootElement), receivesPointerEvents: true },
|
|
elements: /* @__PURE__ */ new Map(),
|
|
refs: /* @__PURE__ */ new Map(),
|
|
iframeRefs: []
|
|
};
|
|
setAriaNodeElement(snapshot.root, rootElement);
|
|
const visit = (ariaNode, node, parentElementVisible) => {
|
|
if (visited.has(node))
|
|
return;
|
|
visited.add(node);
|
|
if (node.nodeType === Node.TEXT_NODE && node.nodeValue) {
|
|
if (!parentElementVisible)
|
|
return;
|
|
const text = node.nodeValue;
|
|
if (ariaNode.role !== "textbox" && text)
|
|
ariaNode.children.push(node.nodeValue || "");
|
|
return;
|
|
}
|
|
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
return;
|
|
const element = node;
|
|
const isElementVisibleForAria = !isElementHiddenForAria(element);
|
|
let visible = isElementVisibleForAria;
|
|
if (options.visibility === "ariaOrVisible")
|
|
visible = isElementVisibleForAria || isElementVisible(element);
|
|
if (options.visibility === "ariaAndVisible")
|
|
visible = isElementVisibleForAria && isElementVisible(element);
|
|
if (options.visibility === "aria" && !visible)
|
|
return;
|
|
const ariaChildren = [];
|
|
if (element.hasAttribute("aria-owns")) {
|
|
const ids = element.getAttribute("aria-owns").split(/\s+/);
|
|
for (const id of ids) {
|
|
const ownedElement = rootElement.ownerDocument.getElementById(id);
|
|
if (ownedElement)
|
|
ariaChildren.push(ownedElement);
|
|
}
|
|
}
|
|
const childAriaNode = visible ? toAriaNode(element, options) : null;
|
|
if (childAriaNode) {
|
|
if (childAriaNode.ref) {
|
|
snapshot.elements.set(childAriaNode.ref, element);
|
|
snapshot.refs.set(element, childAriaNode.ref);
|
|
if (childAriaNode.role === "iframe")
|
|
snapshot.iframeRefs.push(childAriaNode.ref);
|
|
}
|
|
ariaNode.children.push(childAriaNode);
|
|
}
|
|
processElement(childAriaNode || ariaNode, element, ariaChildren, visible);
|
|
};
|
|
function processElement(ariaNode, element, ariaChildren, parentElementVisible) {
|
|
const display = getElementComputedStyle(element)?.display || "inline";
|
|
const treatAsBlock = display !== "inline" || element.nodeName === "BR" ? " " : "";
|
|
if (treatAsBlock)
|
|
ariaNode.children.push(treatAsBlock);
|
|
ariaNode.children.push(getCSSContent(element, "::before") || "");
|
|
const assignedNodes = element.nodeName === "SLOT" ? element.assignedNodes() : [];
|
|
if (assignedNodes.length) {
|
|
for (const child of assignedNodes)
|
|
visit(ariaNode, child, parentElementVisible);
|
|
} else {
|
|
for (let child = element.firstChild; child; child = child.nextSibling) {
|
|
if (!child.assignedSlot)
|
|
visit(ariaNode, child, parentElementVisible);
|
|
}
|
|
if (element.shadowRoot) {
|
|
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
|
|
visit(ariaNode, child, parentElementVisible);
|
|
}
|
|
}
|
|
for (const child of ariaChildren)
|
|
visit(ariaNode, child, parentElementVisible);
|
|
ariaNode.children.push(getCSSContent(element, "::after") || "");
|
|
if (treatAsBlock)
|
|
ariaNode.children.push(treatAsBlock);
|
|
if (ariaNode.children.length === 1 && ariaNode.name === ariaNode.children[0])
|
|
ariaNode.children = [];
|
|
if (ariaNode.role === "link" && element.hasAttribute("href")) {
|
|
const href = element.getAttribute("href");
|
|
ariaNode.props["url"] = href;
|
|
}
|
|
if (ariaNode.role === "textbox" && element.hasAttribute("placeholder") && element.getAttribute("placeholder") !== ariaNode.name) {
|
|
const placeholder = element.getAttribute("placeholder");
|
|
ariaNode.props["placeholder"] = placeholder;
|
|
}
|
|
}
|
|
beginAriaCaches();
|
|
try {
|
|
visit(snapshot.root, rootElement, true);
|
|
} finally {
|
|
endAriaCaches();
|
|
}
|
|
normalizeStringChildren(snapshot.root);
|
|
normalizeGenericRoles(snapshot.root);
|
|
return snapshot;
|
|
}
|
|
function computeAriaRef(ariaNode, options) {
|
|
if (options.refs === "none")
|
|
return;
|
|
if (options.refs === "interactable" && (!ariaNode.box.visible || !ariaNode.receivesPointerEvents))
|
|
return;
|
|
const element = ariaNodeElement(ariaNode);
|
|
let ariaRef = element._ariaRef;
|
|
if (!ariaRef || ariaRef.role !== ariaNode.role || ariaRef.name !== ariaNode.name) {
|
|
ariaRef = { role: ariaNode.role, name: ariaNode.name, ref: (options.refPrefix ?? "") + "e" + ++lastRef };
|
|
element._ariaRef = ariaRef;
|
|
}
|
|
ariaNode.ref = ariaRef.ref;
|
|
}
|
|
function toAriaNode(element, options) {
|
|
const active = element.ownerDocument.activeElement === element;
|
|
if (element.nodeName === "IFRAME") {
|
|
const ariaNode = {
|
|
role: "iframe",
|
|
name: "",
|
|
children: [],
|
|
props: {},
|
|
box: computeBox(element),
|
|
receivesPointerEvents: true,
|
|
active
|
|
};
|
|
setAriaNodeElement(ariaNode, element);
|
|
computeAriaRef(ariaNode, options);
|
|
return ariaNode;
|
|
}
|
|
const defaultRole = options.includeGenericRole ? "generic" : null;
|
|
const role = getAriaRole(element) ?? defaultRole;
|
|
if (!role || role === "presentation" || role === "none")
|
|
return null;
|
|
const name = normalizeWhiteSpace(getElementAccessibleName(element, false) || "");
|
|
const receivesPointerEvents2 = receivesPointerEvents(element);
|
|
const box = computeBox(element);
|
|
if (role === "generic" && box.inline && element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE)
|
|
return null;
|
|
const result = {
|
|
role,
|
|
name,
|
|
children: [],
|
|
props: {},
|
|
box,
|
|
receivesPointerEvents: receivesPointerEvents2,
|
|
active
|
|
};
|
|
setAriaNodeElement(result, element);
|
|
computeAriaRef(result, options);
|
|
if (kAriaCheckedRoles.includes(role))
|
|
result.checked = getAriaChecked(element);
|
|
if (kAriaDisabledRoles.includes(role))
|
|
result.disabled = getAriaDisabled(element);
|
|
if (kAriaExpandedRoles.includes(role))
|
|
result.expanded = getAriaExpanded(element);
|
|
if (kAriaInvalidRoles.includes(role)) {
|
|
const invalid = getAriaInvalid(element);
|
|
result.invalid = invalid === "false" ? false : invalid === "true" ? true : invalid;
|
|
}
|
|
if (kAriaLevelRoles.includes(role))
|
|
result.level = getAriaLevel(element);
|
|
if (kAriaPressedRoles.includes(role))
|
|
result.pressed = getAriaPressed(element);
|
|
if (kAriaSelectedRoles.includes(role))
|
|
result.selected = getAriaSelected(element);
|
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
if (element.type !== "checkbox" && element.type !== "radio" && element.type !== "file")
|
|
result.children = [element.value];
|
|
}
|
|
return result;
|
|
}
|
|
function normalizeGenericRoles(node) {
|
|
const normalizeChildren = (node2) => {
|
|
const result = [];
|
|
for (const child of node2.children || []) {
|
|
if (typeof child === "string") {
|
|
result.push(child);
|
|
continue;
|
|
}
|
|
const normalized = normalizeChildren(child);
|
|
result.push(...normalized);
|
|
}
|
|
const removeSelf = node2.role === "generic" && !node2.name && result.length <= 1 && result.every((c) => typeof c !== "string" && !!c.ref);
|
|
if (removeSelf)
|
|
return result;
|
|
node2.children = result;
|
|
return [node2];
|
|
};
|
|
normalizeChildren(node);
|
|
}
|
|
function normalizeStringChildren(rootA11yNode) {
|
|
const flushChildren = (buffer, normalizedChildren) => {
|
|
if (!buffer.length)
|
|
return;
|
|
const text = normalizeWhiteSpace(buffer.join(""));
|
|
if (text)
|
|
normalizedChildren.push(text);
|
|
buffer.length = 0;
|
|
};
|
|
const visit = (ariaNode) => {
|
|
const normalizedChildren = [];
|
|
const buffer = [];
|
|
for (const child of ariaNode.children || []) {
|
|
if (typeof child === "string") {
|
|
buffer.push(child);
|
|
} else {
|
|
flushChildren(buffer, normalizedChildren);
|
|
visit(child);
|
|
normalizedChildren.push(child);
|
|
}
|
|
}
|
|
flushChildren(buffer, normalizedChildren);
|
|
ariaNode.children = normalizedChildren.length ? normalizedChildren : [];
|
|
if (ariaNode.children.length === 1 && ariaNode.children[0] === ariaNode.name)
|
|
ariaNode.children = [];
|
|
};
|
|
visit(rootA11yNode);
|
|
}
|
|
function buildByRefMap(root, map = /* @__PURE__ */ new Map()) {
|
|
if (root?.ref)
|
|
map.set(root.ref, root);
|
|
for (const child of root?.children || []) {
|
|
if (typeof child !== "string")
|
|
buildByRefMap(child, map);
|
|
}
|
|
return map;
|
|
}
|
|
function compareSnapshots(ariaSnapshot, previousSnapshot) {
|
|
const previousByRef = buildByRefMap(previousSnapshot?.root);
|
|
const result = /* @__PURE__ */ new Map();
|
|
const visit = (ariaNode, previousNode) => {
|
|
let same = ariaNode.children.length === previousNode?.children.length && ariaNodesEqual(ariaNode, previousNode);
|
|
let canBeSkipped = same;
|
|
for (let childIndex = 0; childIndex < ariaNode.children.length; childIndex++) {
|
|
const child = ariaNode.children[childIndex];
|
|
const previousChild = previousNode?.children[childIndex];
|
|
if (typeof child === "string") {
|
|
same &&= child === previousChild;
|
|
canBeSkipped &&= child === previousChild;
|
|
} else {
|
|
let previous = typeof previousChild !== "string" ? previousChild : void 0;
|
|
if (child.ref)
|
|
previous = previousByRef.get(child.ref);
|
|
const sameChild = visit(child, previous);
|
|
if (!previous || !sameChild && !child.ref || previous !== previousChild)
|
|
canBeSkipped = false;
|
|
same &&= sameChild && previous === previousChild;
|
|
}
|
|
}
|
|
result.set(ariaNode, same ? "same" : canBeSkipped ? "skip" : "changed");
|
|
return same;
|
|
};
|
|
visit(ariaSnapshot.root, previousByRef.get(previousSnapshot?.root?.ref));
|
|
return result;
|
|
}
|
|
function filterSnapshotDiff(nodes, statusMap) {
|
|
const result = [];
|
|
const visit = (ariaNode) => {
|
|
const status = statusMap.get(ariaNode);
|
|
if (status === "same") {
|
|
} else if (status === "skip") {
|
|
for (const child of ariaNode.children) {
|
|
if (typeof child !== "string")
|
|
visit(child);
|
|
}
|
|
} else {
|
|
result.push(ariaNode);
|
|
}
|
|
};
|
|
for (const node of nodes) {
|
|
if (typeof node === "string")
|
|
result.push(node);
|
|
else
|
|
visit(node);
|
|
}
|
|
return result;
|
|
}
|
|
function indent(depth) {
|
|
return " ".repeat(depth);
|
|
}
|
|
function renderAriaTree(ariaSnapshot, publicOptions, previousSnapshot) {
|
|
const options = toInternalOptions(publicOptions);
|
|
const lines = [];
|
|
const iframeDepths = {};
|
|
const includeText = options.renderStringsAsRegex ? textContributesInfo : () => true;
|
|
const renderString = options.renderStringsAsRegex ? convertToBestGuessRegex : (str) => str;
|
|
let nodesToRender = ariaSnapshot.root.role === "fragment" ? ariaSnapshot.root.children : [ariaSnapshot.root];
|
|
const statusMap = compareSnapshots(ariaSnapshot, previousSnapshot);
|
|
if (previousSnapshot)
|
|
nodesToRender = filterSnapshotDiff(nodesToRender, statusMap);
|
|
const visitText = (text, depth) => {
|
|
if (publicOptions.depth && depth > publicOptions.depth)
|
|
return;
|
|
const escaped = yamlEscapeValueIfNeeded(renderString(text));
|
|
if (escaped)
|
|
lines.push(indent(depth) + "- text: " + escaped);
|
|
};
|
|
const createKey = (ariaNode, renderCursorPointer) => {
|
|
let key = ariaNode.role;
|
|
if (ariaNode.name && ariaNode.name.length <= 900) {
|
|
const name = renderString(ariaNode.name);
|
|
if (name) {
|
|
const stringifiedName = name.startsWith("/") && name.endsWith("/") ? name : JSON.stringify(name);
|
|
key += " " + stringifiedName;
|
|
}
|
|
}
|
|
if (ariaNode.checked === "mixed")
|
|
key += ` [checked=mixed]`;
|
|
if (ariaNode.checked === true)
|
|
key += ` [checked]`;
|
|
if (ariaNode.disabled)
|
|
key += ` [disabled]`;
|
|
if (ariaNode.expanded)
|
|
key += ` [expanded]`;
|
|
if (ariaNode.active && options.renderActive)
|
|
key += ` [active]`;
|
|
if (ariaNode.invalid === "grammar" || ariaNode.invalid === "spelling")
|
|
key += ` [invalid=${ariaNode.invalid}]`;
|
|
if (ariaNode.invalid === true)
|
|
key += ` [invalid]`;
|
|
if (ariaNode.level)
|
|
key += ` [level=${ariaNode.level}]`;
|
|
if (ariaNode.pressed === "mixed")
|
|
key += ` [pressed=mixed]`;
|
|
if (ariaNode.pressed === true)
|
|
key += ` [pressed]`;
|
|
if (ariaNode.selected === true)
|
|
key += ` [selected]`;
|
|
if (ariaNode.ref) {
|
|
key += ` [ref=${ariaNode.ref}]`;
|
|
if (renderCursorPointer && hasPointerCursor(ariaNode))
|
|
key += " [cursor=pointer]";
|
|
}
|
|
if (options.renderBoxes) {
|
|
const element = ariaNodeElement(ariaNode);
|
|
if (element) {
|
|
const r = element.getBoundingClientRect();
|
|
key += ` [box=${Math.round(r.x)},${Math.round(r.y)},${Math.round(r.width)},${Math.round(r.height)}]`;
|
|
}
|
|
}
|
|
return key;
|
|
};
|
|
const getSingleInlinedTextChild = (ariaNode) => {
|
|
return ariaNode?.children.length === 1 && typeof ariaNode.children[0] === "string" && !Object.keys(ariaNode.props).length ? ariaNode.children[0] : void 0;
|
|
};
|
|
const visit = (ariaNode, depth, renderCursorPointer) => {
|
|
if (publicOptions.depth && depth > publicOptions.depth)
|
|
return;
|
|
if (ariaNode.role === "iframe" && ariaNode.ref)
|
|
iframeDepths[ariaNode.ref] = depth;
|
|
if (statusMap.get(ariaNode) === "same" && ariaNode.ref) {
|
|
lines.push(indent(depth) + `- ref=${ariaNode.ref} [unchanged]`);
|
|
return;
|
|
}
|
|
const isDiffRoot = !!previousSnapshot && !depth;
|
|
const escapedKey = indent(depth) + "- " + (isDiffRoot ? "<changed> " : "") + yamlEscapeKeyIfNeeded(createKey(ariaNode, renderCursorPointer));
|
|
const singleInlinedTextChild = getSingleInlinedTextChild(ariaNode);
|
|
const isAtDepthLimit = !!publicOptions.depth && depth === publicOptions.depth;
|
|
const hasNoChildren = !singleInlinedTextChild && (!ariaNode.children.length || isAtDepthLimit);
|
|
if (hasNoChildren && !Object.keys(ariaNode.props).length) {
|
|
lines.push(escapedKey);
|
|
} else if (singleInlinedTextChild !== void 0) {
|
|
const shouldInclude = includeText(ariaNode, singleInlinedTextChild);
|
|
if (shouldInclude)
|
|
lines.push(escapedKey + ": " + yamlEscapeValueIfNeeded(renderString(singleInlinedTextChild)));
|
|
else
|
|
lines.push(escapedKey);
|
|
} else {
|
|
lines.push(escapedKey + ":");
|
|
for (const [name, value] of Object.entries(ariaNode.props))
|
|
lines.push(indent(depth + 1) + "- /" + name + ": " + yamlEscapeValueIfNeeded(value));
|
|
const inCursorPointer = !!ariaNode.ref && renderCursorPointer && hasPointerCursor(ariaNode);
|
|
for (const child of ariaNode.children) {
|
|
if (typeof child === "string")
|
|
visitText(includeText(ariaNode, child) ? child : "", depth + 1);
|
|
else
|
|
visit(child, depth + 1, renderCursorPointer && !inCursorPointer);
|
|
}
|
|
}
|
|
};
|
|
for (const nodeToRender of nodesToRender) {
|
|
if (typeof nodeToRender === "string")
|
|
visitText(nodeToRender, 0);
|
|
else
|
|
visit(nodeToRender, 0, !!options.renderCursorPointer);
|
|
}
|
|
return { text: lines.join("\n"), iframeDepths };
|
|
}
|
|
function convertToBestGuessRegex(text) {
|
|
const dynamicContent = [
|
|
// 550e8400-e29b-41d4-a716-446655440000
|
|
{ regex: /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/, replacement: "[0-9a-fA-F-]+" },
|
|
// 2mb
|
|
{ regex: /\b[\d,.]+[bkmBKM]+\b/, replacement: "[\\d,.]+[bkmBKM]+" },
|
|
// 2ms, 20s
|
|
{ regex: /\b\d+[hmsp]+\b/, replacement: "\\d+[hmsp]+" },
|
|
{ regex: /\b[\d,.]+[hmsp]+\b/, replacement: "[\\d,.]+[hmsp]+" },
|
|
// Do not replace single digits with regex by default.
|
|
// 2+ digits: [Issue 22, 22.3, 2.33, 2,333]
|
|
{ regex: /\b\d+,\d+\b/, replacement: "\\d+,\\d+" },
|
|
{ regex: /\b\d+\.\d{2,}\b/, replacement: "\\d+\\.\\d+" },
|
|
{ regex: /\b\d{2,}\.\d+\b/, replacement: "\\d+\\.\\d+" },
|
|
{ regex: /\b\d{2,}\b/, replacement: "\\d+" }
|
|
];
|
|
let pattern = "";
|
|
let lastIndex = 0;
|
|
const combinedRegex = new RegExp(dynamicContent.map((r) => "(" + r.regex.source + ")").join("|"), "g");
|
|
text.replace(combinedRegex, (match, ...args) => {
|
|
const offset = args[args.length - 2];
|
|
const groups = args.slice(0, -2);
|
|
pattern += escapeRegExp(text.slice(lastIndex, offset));
|
|
for (let i = 0; i < groups.length; i++) {
|
|
if (groups[i]) {
|
|
const { replacement } = dynamicContent[i];
|
|
pattern += replacement;
|
|
break;
|
|
}
|
|
}
|
|
lastIndex = offset + match.length;
|
|
return match;
|
|
});
|
|
if (!pattern)
|
|
return text;
|
|
pattern += escapeRegExp(text.slice(lastIndex));
|
|
return String(new RegExp(pattern));
|
|
}
|
|
function textContributesInfo(node, text) {
|
|
if (!text.length)
|
|
return false;
|
|
if (!node.name)
|
|
return true;
|
|
const substr = text.length <= 200 && node.name.length <= 200 ? longestCommonSubstring(text, node.name) : "";
|
|
let filtered = text;
|
|
while (substr && filtered.includes(substr))
|
|
filtered = filtered.replace(substr, "");
|
|
return filtered.trim().length / text.length > 0.1;
|
|
}
|
|
var elementSymbol = /* @__PURE__ */ Symbol("element");
|
|
function ariaNodeElement(ariaNode) {
|
|
return ariaNode[elementSymbol];
|
|
}
|
|
function setAriaNodeElement(ariaNode, element) {
|
|
ariaNode[elementSymbol] = element;
|
|
}
|
|
|
|
// esm-entry.ts
|
|
function buildSnapshot(root, options) {
|
|
const o = Object.assign({ mode: "ai" }, options || {});
|
|
const tree = generateAriaTree(root || document.body, o);
|
|
const rendered = renderAriaTree(tree, o);
|
|
return {
|
|
text: rendered.text,
|
|
elements: tree.elements,
|
|
// Map<string ref, Element>
|
|
refs: tree.refs
|
|
// Map<Element, string ref>
|
|
};
|
|
}
|
|
export {
|
|
buildSnapshot,
|
|
generateAriaTree,
|
|
renderAriaTree
|
|
};
|