browser-debugger-cli 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/.claude/skills/bdg/SKILL.md +4 -4
  2. package/README.md +143 -79
  3. package/dist/commands/cdp.d.ts +22 -1
  4. package/dist/commands/cdp.js +100 -43
  5. package/dist/commands/console.d.ts +12 -0
  6. package/dist/commands/console.js +62 -12
  7. package/dist/commands/css.d.ts +13 -0
  8. package/dist/commands/css.js +53 -0
  9. package/dist/commands/dom/DomElementResolver.d.ts +3 -1
  10. package/dist/commands/dom/DomElementResolver.js +10 -3
  11. package/dist/commands/dom/a11y.js +3 -2
  12. package/dist/commands/dom/audit.d.ts +14 -0
  13. package/dist/commands/dom/audit.js +87 -0
  14. package/dist/commands/dom/eval.d.ts +3 -2
  15. package/dist/commands/dom/eval.js +11 -5
  16. package/dist/commands/dom/form.js +10 -9
  17. package/dist/commands/dom/formInteraction.js +42 -11
  18. package/dist/commands/dom/get.js +8 -8
  19. package/dist/commands/dom/helpers/index.d.ts +1 -1
  20. package/dist/commands/dom/helpers/index.js +1 -1
  21. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  22. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  23. package/dist/commands/dom/helpers/query.d.ts +27 -3
  24. package/dist/commands/dom/helpers/query.js +152 -64
  25. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  26. package/dist/commands/dom/helpers/screenshot.js +169 -49
  27. package/dist/commands/dom/index.js +7 -2
  28. package/dist/commands/dom/query.d.ts +19 -2
  29. package/dist/commands/dom/query.js +37 -6
  30. package/dist/commands/dom/screenshot.js +12 -7
  31. package/dist/commands/dom/semanticUtils.d.ts +3 -2
  32. package/dist/commands/dom/semanticUtils.js +40 -9
  33. package/dist/commands/dom/wait.js +5 -3
  34. package/dist/commands/helpJson.d.ts +82 -19
  35. package/dist/commands/helpJson.js +112 -41
  36. package/dist/commands/helpTopic.d.ts +16 -1
  37. package/dist/commands/helpTopic.js +59 -1
  38. package/dist/commands/installSkill.d.ts +15 -5
  39. package/dist/commands/installSkill.js +86 -16
  40. package/dist/commands/network/list.js +22 -12
  41. package/dist/commands/optionBehaviors.js +53 -16
  42. package/dist/commands/page.js +7 -4
  43. package/dist/commands/peek.d.ts +7 -0
  44. package/dist/commands/peek.js +65 -23
  45. package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
  46. package/dist/commands/shared/daemonErrorHandler.js +20 -9
  47. package/dist/commands/shared/dataFetcher.d.ts +12 -4
  48. package/dist/commands/shared/dataFetcher.js +12 -4
  49. package/dist/commands/shared/followMode.d.ts +9 -1
  50. package/dist/commands/shared/followMode.js +22 -4
  51. package/dist/commands/shared/optionTypes.d.ts +9 -2
  52. package/dist/commands/shared/outputFile.js +6 -1
  53. package/dist/commands/start.d.ts +20 -5
  54. package/dist/commands/start.js +84 -23
  55. package/dist/commands/stop.d.ts +11 -0
  56. package/dist/commands/stop.js +24 -1
  57. package/dist/commands/tail.d.ts +7 -1
  58. package/dist/commands/tail.js +13 -62
  59. package/dist/commands.js +2 -0
  60. package/dist/connection/cdp.d.ts +7 -0
  61. package/dist/connection/cdp.js +9 -0
  62. package/dist/connection/launcher.js +3 -2
  63. package/dist/daemon/SessionController.js +6 -1
  64. package/dist/daemon/launcher.d.ts +3 -2
  65. package/dist/daemon/launcher.js +47 -3
  66. package/dist/daemon/session/Session.d.ts +4 -1
  67. package/dist/daemon/session/Session.js +33 -2
  68. package/dist/daemon/session/TelemetryStore.d.ts +8 -1
  69. package/dist/daemon/session/TelemetryStore.js +13 -1
  70. package/dist/daemon/session/commandRegistry.js +36 -14
  71. package/dist/daemon/session/interactions.d.ts +2 -1
  72. package/dist/daemon/session/interactions.js +13 -1
  73. package/dist/daemon/session/plugins.js +19 -53
  74. package/dist/daemon/session/teardown.js +1 -1
  75. package/dist/daemon.js +9234 -7222
  76. package/dist/errors/messages.d.ts +88 -15
  77. package/dist/errors/messages.js +177 -27
  78. package/dist/index.js +19322 -13961
  79. package/dist/ipc/client.d.ts +22 -2
  80. package/dist/ipc/client.js +34 -5
  81. package/dist/ipc/protocol/auditTypes.d.ts +135 -0
  82. package/dist/ipc/protocol/auditTypes.js +6 -0
  83. package/dist/ipc/protocol/commands.d.ts +35 -0
  84. package/dist/ipc/protocol/commands.js +2 -0
  85. package/dist/ipc/protocol/domTypes.d.ts +16 -0
  86. package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
  87. package/dist/ipc/session/types.d.ts +2 -0
  88. package/dist/runtime/css/search.d.ts +39 -0
  89. package/dist/runtime/css/search.js +122 -0
  90. package/dist/runtime/dom/actionEffects.d.ts +9 -2
  91. package/dist/runtime/dom/actionEffects.js +30 -14
  92. package/dist/runtime/dom/audit.d.ts +19 -0
  93. package/dist/runtime/dom/audit.js +37 -0
  94. package/dist/runtime/dom/auditModel.d.ts +45 -0
  95. package/dist/runtime/dom/auditModel.js +220 -0
  96. package/dist/runtime/dom/auditScripts.d.ts +113 -0
  97. package/dist/runtime/dom/auditScripts.js +148 -0
  98. package/dist/runtime/dom/elementGeometry.d.ts +16 -3
  99. package/dist/runtime/dom/elementGeometry.js +49 -10
  100. package/dist/runtime/dom/elementInfo.d.ts +74 -17
  101. package/dist/runtime/dom/elementInfo.js +187 -34
  102. package/dist/runtime/dom/evalHelpers.d.ts +12 -2
  103. package/dist/runtime/dom/evalHelpers.js +67 -7
  104. package/dist/runtime/dom/formDiscovery.d.ts +6 -2
  105. package/dist/runtime/dom/formDiscovery.js +20 -3
  106. package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
  107. package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
  108. package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
  109. package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
  110. package/dist/runtime/dom/formSubmitHelpers.js +4 -3
  111. package/dist/runtime/dom/frameLayout.js +1 -0
  112. package/dist/runtime/dom/inspect.d.ts +7 -0
  113. package/dist/runtime/dom/inspect.js +92 -28
  114. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  115. package/dist/runtime/dom/inspectAllStyles.js +90 -7
  116. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  117. package/dist/runtime/dom/inspectCascade.js +214 -44
  118. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  119. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  120. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  121. package/dist/runtime/dom/inspectHints.js +125 -9
  122. package/dist/runtime/dom/inspectModel.d.ts +5 -1
  123. package/dist/runtime/dom/inspectModel.js +37 -10
  124. package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
  125. package/dist/runtime/dom/inspectPaintModel.js +182 -68
  126. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  127. package/dist/runtime/dom/inspectRules.js +21 -5
  128. package/dist/runtime/dom/inspectScripts.d.ts +112 -12
  129. package/dist/runtime/dom/inspectScripts.js +357 -32
  130. package/dist/runtime/dom/inspectTree.js +10 -2
  131. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  132. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  133. package/dist/runtime/dom/layout.js +40 -16
  134. package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
  135. package/dist/runtime/dom/reactEventHelpers.js +90 -36
  136. package/dist/runtime/dom/targetNode.d.ts +18 -5
  137. package/dist/runtime/dom/targetNode.js +268 -8
  138. package/dist/runtime/dom/wait.js +2 -1
  139. package/dist/runtime/page/bdgWorld.d.ts +57 -0
  140. package/dist/runtime/page/bdgWorld.js +180 -0
  141. package/dist/runtime/page/emulation.d.ts +13 -4
  142. package/dist/runtime/page/emulation.js +69 -4
  143. package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
  144. package/dist/runtime/page/replacedBuiltins.js +136 -0
  145. package/dist/runtime/page/userAgent.d.ts +17 -0
  146. package/dist/runtime/page/userAgent.js +57 -0
  147. package/dist/session/QueryCacheManager.d.ts +4 -1
  148. package/dist/session/QueryCacheManager.js +5 -2
  149. package/dist/session/chrome.d.ts +4 -1
  150. package/dist/session/chrome.js +7 -1
  151. package/dist/session/cleanup/staleSession.d.ts +21 -4
  152. package/dist/session/cleanup/staleSession.js +79 -9
  153. package/dist/session/cleanup/userCommands.d.ts +4 -1
  154. package/dist/session/cleanup/userCommands.js +10 -5
  155. package/dist/session/daemonSocket.d.ts +10 -0
  156. package/dist/session/daemonSocket.js +22 -0
  157. package/dist/session/lastSession.d.ts +6 -3
  158. package/dist/session/lastSession.js +11 -5
  159. package/dist/session/paths.d.ts +3 -1
  160. package/dist/session/paths.js +5 -5
  161. package/dist/session/portClaims.js +4 -3
  162. package/dist/session/sessionList.d.ts +13 -5
  163. package/dist/session/sessionList.js +31 -7
  164. package/dist/telemetry/a11y.js +2 -2
  165. package/dist/telemetry/console.d.ts +2 -1
  166. package/dist/telemetry/console.js +30 -21
  167. package/dist/telemetry/pageCrash.d.ts +26 -0
  168. package/dist/telemetry/pageCrash.js +53 -0
  169. package/dist/types.d.ts +20 -0
  170. package/dist/ui/formatters/audit.d.ts +19 -0
  171. package/dist/ui/formatters/audit.js +115 -0
  172. package/dist/ui/formatters/cdp.d.ts +138 -0
  173. package/dist/ui/formatters/cdp.js +131 -0
  174. package/dist/ui/formatters/console/chronological.js +3 -1
  175. package/dist/ui/formatters/console/follow.d.ts +2 -1
  176. package/dist/ui/formatters/console/follow.js +2 -2
  177. package/dist/ui/formatters/console/json.d.ts +2 -2
  178. package/dist/ui/formatters/console/json.js +11 -5
  179. package/dist/ui/formatters/console/shared.d.ts +30 -0
  180. package/dist/ui/formatters/console/shared.js +16 -0
  181. package/dist/ui/formatters/console/summarize.d.ts +9 -2
  182. package/dist/ui/formatters/console/summarize.js +40 -9
  183. package/dist/ui/formatters/console.d.ts +2 -1
  184. package/dist/ui/formatters/console.js +7 -5
  185. package/dist/ui/formatters/details.js +3 -1
  186. package/dist/ui/formatters/dom.d.ts +2 -2
  187. package/dist/ui/formatters/dom.js +10 -8
  188. package/dist/ui/formatters/helpFormatters.js +1 -1
  189. package/dist/ui/formatters/inspect.js +50 -17
  190. package/dist/ui/formatters/installSkill.d.ts +9 -1
  191. package/dist/ui/formatters/installSkill.js +32 -6
  192. package/dist/ui/formatters/layout.js +2 -1
  193. package/dist/ui/formatters/networkList.d.ts +1 -1
  194. package/dist/ui/formatters/networkList.js +1 -2
  195. package/dist/ui/formatters/preview.d.ts +2 -0
  196. package/dist/ui/formatters/preview.js +17 -7
  197. package/dist/ui/formatters/sessions.d.ts +2 -2
  198. package/dist/ui/formatters/sessions.js +9 -2
  199. package/dist/ui/formatters/status.js +1 -1
  200. package/dist/ui/logging/logger.d.ts +1 -1
  201. package/dist/ui/messages/commands.d.ts +168 -11
  202. package/dist/ui/messages/commands.js +245 -18
  203. package/dist/ui/messages/consoleMessages.d.ts +24 -0
  204. package/dist/ui/messages/consoleMessages.js +32 -0
  205. package/dist/ui/messages/preview.d.ts +12 -0
  206. package/dist/ui/messages/preview.js +18 -2
  207. package/dist/ui/messages/session.d.ts +13 -2
  208. package/dist/ui/messages/session.js +22 -3
  209. package/dist/utils/cssValues.js +36 -4
  210. package/dist/utils/decisionTrees.js +0 -5
  211. package/dist/utils/directories.d.ts +34 -0
  212. package/dist/utils/directories.js +88 -0
  213. package/dist/utils/display.d.ts +16 -0
  214. package/dist/utils/display.js +42 -0
  215. package/dist/utils/exitCodes.d.ts +1 -0
  216. package/dist/utils/exitCodes.js +6 -0
  217. package/dist/utils/process.d.ts +12 -0
  218. package/dist/utils/process.js +25 -0
  219. package/dist/utils/suggestions.d.ts +4 -2
  220. package/dist/utils/suggestions.js +7 -5
  221. package/dist/utils/taskMappings.js +1 -1
  222. package/package.json +3 -2
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Page-side part of `bdg dom audit`: one walk over the rendered elements of
3
+ * the page (open shadow roots and the light DOM slotted into them, at most
4
+ * {@link AUDIT_ELEMENT_CAP}; nothing inside a fixed element counts as making
5
+ * the page wider; SVG images and placeholders of 2px or less are skipped)
6
+ * that collects what each check needs. Colors and contrast are computed in
7
+ * the daemon ({@link buildAudit}), the same way `dom inspect` computes them.
8
+ */
9
+ import { LABEL_CLASSES_JS } from './elementInfo.js';
10
+ import { FLAT_TREE_JS, HIT_EVERYTHING_JS, PAINT_RISKS_JS, } from './inspectScripts.js';
11
+ import { AUDIT_OUT_OF_VIEW_RISK } from '../../ui/messages/commands.js';
12
+ /** Elements the walk looks at, at most */
13
+ export const AUDIT_ELEMENT_CAP = 20000;
14
+ /**
15
+ * Page-side audit walk. Arguments: the checks to collect for
16
+ * (`contrast`, `overflow`, `layers`, `animations`).
17
+ */
18
+ export const AUDIT_PAGE_JS = `function (checks) {
19
+ const want = (name) => checks.includes(name);
20
+ const doc = document;
21
+ const view = window;
22
+ const style = (n) => view.getComputedStyle(n);
23
+ const scroller = doc.scrollingElement || doc.documentElement;
24
+ const viewport = { width: doc.documentElement.clientWidth, height: view.innerHeight };
25
+ const firstClass = (n) => (${LABEL_CLASSES_JS})(n)[0];
26
+ const label = (n) => n.localName + (n.id ? '#' + n.id : firstClass(n) ? '.' + firstClass(n) : '');
27
+ const parentOf = (n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null;
28
+ const short = (text) => text.replace(/\\s+/g, ' ').trim().slice(0, 60);
29
+ const ownText = (n) => Array.from(n.childNodes).filter((c) => c.nodeType === 3).map((c) => c.data).join(' ').replace(/\\s+/g, ' ').trim();
30
+ const inView = (r) => r.bottom > 0 && r.right > 0 && r.top < viewport.height && r.left < viewport.width;
31
+ const meta = doc.querySelector('meta[name="color-scheme"]');
32
+ const rootScheme = style(doc.documentElement).colorScheme;
33
+ const scheme = rootScheme && rootScheme !== 'normal' ? rootScheme : (meta && meta.content) || '';
34
+ const canvasDark = /dark/.test(scheme) && (!/light/.test(scheme) || view.matchMedia('(prefers-color-scheme: dark)').matches);
35
+ const result = { viewport: viewport, pixelRatio: view.devicePixelRatio || 1, pageWidth: scroller.scrollWidth, canvasDark: canvasDark, walked: 0, capped: false };
36
+ const texts = [], wide = [], truncated = [], images = [], layers = [], scrollers = [];
37
+ let canvases = 0;
38
+ const tree = (${FLAT_TREE_JS})(view);
39
+ const paintRisks = ${PAINT_RISKS_JS};
40
+ const clear = (color) => color === 'transparent' || /^rgba\\(.*,\\s*0\\)$/.test(color) || /\\/\\s*0\\)$/.test(color);
41
+ const chainOf = (n) => {
42
+ const backgrounds = [];
43
+ const risks = [];
44
+ let opacity = 1;
45
+ let painted = false;
46
+ let solid = false;
47
+ for (let p = n; p && backgrounds.length < 60; p = parentOf(p)) {
48
+ const s = style(p);
49
+ const own = Number(s.opacity) || 0;
50
+ const background = { color: s.backgroundColor, image: s.backgroundImage !== 'none' };
51
+ backgrounds.push(own < 1 ? Object.assign(background, { opacity: own }) : background);
52
+ opacity *= own;
53
+ if (p !== doc.documentElement && p !== doc.body && (background.image || !clear(s.backgroundColor))) painted = true;
54
+ if (p !== doc.documentElement && p !== doc.body && !background.image && /^rgb\\(/.test(s.backgroundColor)) solid = true;
55
+ if (s.mixBlendMode !== 'normal') risks.push('mix-blend-mode ' + s.mixBlendMode + ' on ' + label(p));
56
+ if (s.filter !== 'none') risks.push('filter on ' + label(p));
57
+ }
58
+ return { backgrounds: backgrounds, risks: risks, opacity: opacity, painted: painted, solid: solid };
59
+ };
60
+ const unhit = [];
61
+ const painters = [];
62
+ const markPaintedBehind = () => {
63
+ for (const text of unhit) {
64
+ const painter = painters.find((p) => p.el !== text.el && !p.el.contains(text.el) && !text.el.contains(p.el) &&
65
+ text.x >= p.left && text.x <= p.right && text.y >= p.top && text.y <= p.bottom);
66
+ if (painter && !text.entry.risks.includes(${JSON.stringify(AUDIT_OUT_OF_VIEW_RISK)})) text.entry.risks.push(label(painter.el) + ' under or over it (out of view, not hit-tested)');
67
+ }
68
+ };
69
+ const risksOf = (n, slot, chain, shown) => {
70
+ if (!shown) return chain.painted ? chain.risks : chain.risks.concat(${JSON.stringify(AUDIT_OUT_OF_VIEW_RISK)});
71
+ const text = slot && n.assignedNodes({ flatten: true }).find((c) => c.nodeType === 3 && c.data.trim() !== '');
72
+ return paintRisks(n, tree, text ? text.parentElement || n : n);
73
+ };
74
+ const visit = (n, fixed) => {
75
+ if (result.walked >= ${AUDIT_ELEMENT_CAP}) { result.capped = true; return; }
76
+ if (/^(script|style|template|noscript|head|meta|link|title)$/.test(n.localName)) return;
77
+ const s = style(n);
78
+ if (s.display === 'none') return;
79
+ result.walked++;
80
+ const inFixed = fixed || s.position === 'fixed';
81
+ const kids = n.shadowRoot ? [...Array.from(n.shadowRoot.children), ...Array.from(n.children)] : Array.from(n.children);
82
+ const slot = n.localName === 'slot';
83
+ const box = slot && parentOf(n) ? parentOf(n) : n;
84
+ const r = box.getBoundingClientRect();
85
+ const shown = r.width > 0 && r.height > 0 && s.visibility === 'visible';
86
+ const gradientText = /rgba\\(0, 0, 0, 0\\)|transparent/.test(s.webkitTextFillColor);
87
+ if (shown && want('contrast') && !gradientText) {
88
+ const text = slot ? short(n.assignedNodes({ flatten: true }).filter((c) => c.nodeType === 3).map((c) => c.data).join(' ')) : n.shadowRoot ? '' : ownText(n);
89
+ if (text && Number(s.opacity) > 0 && r.width > 2 && r.height > 2) {
90
+ const chain = chainOf(n);
91
+ const seen = inView(r);
92
+ if (chain.opacity > 0) {
93
+ texts.push({ label: label(n), text: short(text), color: s.color, fontSize: s.fontSize, fontWeight: s.fontWeight, inView: seen, backgrounds: chain.backgrounds, opacity: chain.opacity, risks: risksOf(n, slot, chain, seen) });
94
+ if (!seen && !chain.solid) unhit.push({ entry: texts[texts.length - 1], el: box, x: (r.left + r.right) / 2 + view.scrollX, y: (r.top + r.bottom) / 2 + view.scrollY });
95
+ }
96
+ }
97
+ }
98
+ if (shown && want('contrast') && (/^(img|video|canvas|picture|iframe)$/.test(n.localName) || s.backgroundImage !== 'none')) {
99
+ painters.push({ el: n, left: r.left + view.scrollX, top: r.top + view.scrollY, right: r.right + view.scrollX, bottom: r.bottom + view.scrollY });
100
+ }
101
+ if (shown && want('overflow')) {
102
+ if (r.right > viewport.width + 1 && !inFixed) wide.push({ label: label(n), right: r.right + view.scrollX, width: r.width });
103
+ const cut = s.overflowX !== 'visible' && n.scrollWidth > n.clientWidth + 1 ? (s.textOverflow === 'ellipsis' ? 'ellipsis' : 'clip')
104
+ : s.webkitLineClamp !== 'none' && n.scrollHeight > n.clientHeight + 1 ? 'clamp' : null;
105
+ const textContent = cut && short(n.innerText || '');
106
+ const visuallyHidden = r.width <= 2 || r.height <= 2;
107
+ if (cut && textContent && !visuallyHidden) truncated.push({ label: label(n), text: textContent, kind: cut });
108
+ const vector = /\\.svg([?#]|$)|^data:image\\/svg/i.test(n.currentSrc || '');
109
+ if (n.localName === 'img' && n.naturalWidth > 2 && n.naturalHeight > 2 && n.complete && !vector) images.push({ label: label(n), natural: { w: n.naturalWidth, h: n.naturalHeight }, rendered: { w: r.width, h: r.height }, objectFit: s.objectFit });
110
+ }
111
+ if (shown && want('overflow') && /^(auto|scroll)$/.test(s.overflowX) && n !== doc.documentElement && n !== doc.body && n.scrollWidth > n.clientWidth + 1) {
112
+ scrollers.push({ label: label(n), scrollWidth: n.scrollWidth, width: n.clientWidth });
113
+ }
114
+ if (want('animations') && shown && n.localName === 'canvas') canvases++;
115
+ if (want('layers') && shown && (s.position === 'fixed' || s.position === 'sticky')) {
116
+ layers.push({ label: label(n), position: s.position, zIndex: s.zIndex, rect: { x: r.left, y: r.top, w: r.width, h: r.height }, inView: inView(r) });
117
+ }
118
+ if (s.overflowX !== 'visible' && s.overflowX !== 'clip' && n !== doc.documentElement && n !== doc.body) {
119
+ const wideBefore = wide.length;
120
+ for (const c of kids) visit(c, inFixed);
121
+ wide.length = wideBefore;
122
+ return;
123
+ }
124
+ for (const c of kids) visit(c, inFixed);
125
+ };
126
+ (${HIT_EVERYTHING_JS})([doc], () => visit(doc.documentElement, false));
127
+ markPaintedBehind();
128
+ if (want('contrast')) result.texts = texts;
129
+ if (want('overflow')) Object.assign(result, { wide: wide, truncated: truncated, images: images, scrollers: scrollers });
130
+ if (want('layers')) result.layers = layers;
131
+ if (want('animations')) {
132
+ result.canvases = canvases;
133
+ result.animations = doc.getAnimations().filter((a) => a.playState === 'running').slice(0, 200).map((a) => {
134
+ const timing = a.effect && a.effect.getTiming ? a.effect.getTiming() : {};
135
+ const target = a.effect && a.effect.target;
136
+ return {
137
+ label: target && target.localName ? label(target) : '(none)',
138
+ name: a.animationName || a.transitionProperty || a.id || 'animation',
139
+ type: a.constructor.name,
140
+ duration: typeof timing.duration === 'number' ? timing.duration : String(timing.duration),
141
+ iterations: timing.iterations === Infinity ? 'infinite' : timing.iterations,
142
+ scrollDriven: Boolean(a.timeline) && a.timeline !== doc.timeline
143
+ };
144
+ });
145
+ }
146
+ return result;
147
+ }`;
148
+ //# sourceMappingURL=auditScripts.js.map
@@ -14,6 +14,13 @@ import type { ViewportPosition } from '../../types.js';
14
14
  export interface ElementGeometry {
15
15
  /** Border box in top-level viewport coordinates */
16
16
  rect: LayoutBox;
17
+ /**
18
+ * For a zero-size element whose children show (an inline list around
19
+ * floated items, a `display: contents` web component whose shadow root
20
+ * shows a dialog): the box around those children in the flat tree, which
21
+ * is what can be seen of it
22
+ */
23
+ content?: LayoutBox | null;
17
24
  /** Area its iframes and overflow-clipping ancestors leave visible, in the same coordinates */
18
25
  clip: LayoutBox | null;
19
26
  /** Whether overlay scrollbars of the clipping containers show along the clip's right and bottom edges */
@@ -27,6 +34,11 @@ export interface ElementGeometry {
27
34
  * e.g. `opacity: 0 on div#menu`
28
35
  */
29
36
  invisible: string | null;
37
+ /**
38
+ * Masked by `mask-image` on it or an ancestor ({@link MASKED_BY_JS}), e.g.
39
+ * `mask-image on div.hero`: part or all of it may not show
40
+ */
41
+ masked?: string | null;
30
42
  /** Inside an `inert` element: shown, but not interactive */
31
43
  inert: boolean;
32
44
  /** Fixed to the top-level viewport (it or a container is `position: fixed`): page scroll does not move it */
@@ -120,7 +132,8 @@ export declare const CLIP_RECT_CUTS_ALL_JS = "(position, clip) => {\n if ((posi
120
132
  * top-level viewport coordinates, its clip by ancestors
121
133
  * ({@link ANCESTOR_CLIP_JS}) and by the viewports of its iframes, why it
122
134
  * cannot be seen at all (not rendered, or inside a clipping container
123
- * collapsed to zero size), why a rendered one is still invisible
135
+ * collapsed to zero size; a `display: contents` element is measured by
136
+ * its children in the flat tree and is hidden only when none shows), why a rendered one is still invisible
124
137
  * ({@link INVISIBLE_REASON_JS}), whether it is inert (an `inert` element
125
138
  * around it, through shadow roots) or fixed to the top-level viewport, and how
126
139
  * far the top-level page can scroll ({@link SCROLL_RANGE_JS}) or what locks
@@ -161,10 +174,10 @@ export declare function scrollTarget(start: number, size: number, viewSize: numb
161
174
  * out of a list), and `hidden` when it is not rendered at all. Being inert
162
175
  * does not change the position: inert elements are shown.
163
176
  *
164
- * @param geometry - Page-side measurements
177
+ * @param measured - Page-side measurements (a zero-size holder is placed by its children)
165
178
  * @param viewport - Top-level viewport size
166
179
  * @returns Position, with the visible share, hidden reason, clipping
167
180
  * ancestor or page scroll when relevant
168
181
  */
169
- export declare function classifyViewportPosition(geometry: ElementGeometry, viewport: LayoutSize): ViewportPlacement;
182
+ export declare function classifyViewportPosition(measured: ElementGeometry, viewport: LayoutSize): ViewportPlacement;
170
183
  //# sourceMappingURL=elementGeometry.d.ts.map
@@ -268,12 +268,28 @@ const INVISIBLE_REASON_JS = `(el, describe) => {
268
268
  }
269
269
  return null;
270
270
  }`;
271
+ /**
272
+ * Page-side mask over an element: the nearest of it and its ancestors in the
273
+ * flat tree (slots, shadow hosts, iframes) with a `mask-image`. How much the
274
+ * mask hides is not evaluated. Null without one.
275
+ */
276
+ const MASKED_BY_JS = `(el, describe) => {
277
+ const flatParent = ${FLAT_PARENT_JS};
278
+ const parentOf = (n) => flatParent(n) || n.ownerDocument.defaultView.frameElement || null;
279
+ for (let n = el; n; n = parentOf(n)) {
280
+ const style = n.ownerDocument.defaultView.getComputedStyle(n);
281
+ const mask = style.maskImage || style.webkitMaskImage;
282
+ if (mask && mask !== 'none') return n === el ? 'mask-image' : 'mask-image on ' + describe(n);
283
+ }
284
+ return null;
285
+ }`;
271
286
  /**
272
287
  * Page-side measurement of an element ({@link ElementGeometry}): its box in
273
288
  * top-level viewport coordinates, its clip by ancestors
274
289
  * ({@link ANCESTOR_CLIP_JS}) and by the viewports of its iframes, why it
275
290
  * cannot be seen at all (not rendered, or inside a clipping container
276
- * collapsed to zero size), why a rendered one is still invisible
291
+ * collapsed to zero size; a `display: contents` element is measured by
292
+ * its children in the flat tree and is hidden only when none shows), why a rendered one is still invisible
277
293
  * ({@link INVISIBLE_REASON_JS}), whether it is inert (an `inert` element
278
294
  * around it, through shadow roots) or fixed to the top-level viewport, and how
279
295
  * far the top-level page can scroll ({@link SCROLL_RANGE_JS}) or what locks
@@ -292,6 +308,7 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
292
308
  const scrollRange = ${SCROLL_RANGE_JS};
293
309
  const scrollLock = ${SCROLL_LOCK_JS};
294
310
  const invisibleReason = ${INVISIBLE_REASON_JS};
311
+ const maskedBy = ${MASKED_BY_JS};
295
312
  const describe = ${ELEMENT_DESCRIPTION_JS};
296
313
  const reasons = ${JSON.stringify(LAYOUT_REASONS)};
297
314
  const styleOf = (node) => node.ownerDocument.defaultView.getComputedStyle(node);
@@ -311,16 +328,34 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
311
328
  }
312
329
  return 'not rendered';
313
330
  };
314
- const hiddenReason = (style, box) => {
331
+ const flatChildren = (node) =>
332
+ Array.from((node.shadowRoot || node).children).flatMap((c) => {
333
+ const shown = c.localName === 'slot' ? c.assignedElements({ flatten: true }) : [c];
334
+ return shown.flatMap((s) => (styleOf(s).display === 'contents' ? flatChildren(s) : [s]));
335
+ });
336
+ const shownChildren = (node) => {
337
+ const s = styleOf(node);
338
+ if (s.overflowX !== 'visible' || s.overflowY !== 'visible') return null;
339
+ let union = null;
340
+ for (const c of flatChildren(node).slice(0, 50)) {
341
+ const r = c.getBoundingClientRect();
342
+ if (r.width <= 0 || r.height <= 0 || styleOf(c).visibility !== 'visible') continue;
343
+ union = union
344
+ ? { left: Math.min(union.left, r.left), top: Math.min(union.top, r.top), right: Math.max(union.right, r.right), bottom: Math.max(union.bottom, r.bottom) }
345
+ : { left: r.left, top: r.top, right: r.right, bottom: r.bottom };
346
+ }
347
+ return union;
348
+ };
349
+ const hiddenReason = (style, box, content) => {
315
350
  if (style.display === 'none') return 'display: none';
316
- if (style.display === 'contents') return 'display: contents (no box of its own)';
351
+ if (style.display === 'contents') return content ? null : 'display: contents (no box of its own)';
317
352
  if (el.getClientRects().length === 0) {
318
353
  return el.tagName === 'OPTION' ? reasons.option : 'not rendered (an ancestor has display: none)';
319
354
  }
320
355
  const skipped = skippedReason(el);
321
356
  if (skipped) return skipped;
322
357
  if (style.visibility !== 'visible') return 'visibility: ' + style.visibility;
323
- if (box.width === 0 || box.height === 0) return 'zero size';
358
+ if ((box.width === 0 || box.height === 0) && !content) return 'zero size';
324
359
  return null;
325
360
  };
326
361
  const isSticky = () => {
@@ -343,7 +378,8 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
343
378
  let clipper = own.clipper;
344
379
  let fixed = own.fixed;
345
380
  let fixedBy = own.fixedBy;
346
- let hidden = hiddenReason(styleOf(el), box) || own.collapsed;
381
+ let content = box.width === 0 || box.height === 0 ? shownChildren(el) : null;
382
+ let hidden = hiddenReason(styleOf(el), box, content) || own.collapsed;
347
383
  let x = 0;
348
384
  let y = 0;
349
385
  for (let view = el.ownerDocument.defaultView; view && view.frameElement; view = view.parent) {
@@ -355,6 +391,7 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
355
391
  overlay = { right: !!unframed && unframed.right <= clip.right && overlay.right, bottom: !!unframed && unframed.bottom <= clip.bottom && overlay.bottom };
356
392
  const offset = frameOffset(frame);
357
393
  rect = shift(rect, offset.x, offset.y);
394
+ content = shift(content, offset.x, offset.y);
358
395
  const outer = ancestorClip(frame, rect, describe);
359
396
  const inner = shift(clip, offset.x, offset.y);
360
397
  clip = intersect(inner, outer.clip);
@@ -371,9 +408,10 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
371
408
  while (top.frameElement) top = top.parent;
372
409
  const toBox = (r) => r && { x: r.left, y: r.top, width: r.right - r.left, height: r.bottom - r.top };
373
410
  const invisible = hidden ? null : invisibleReason(el, describe);
411
+ const masked = hidden || invisible ? null : maskedBy(el, describe);
374
412
  const lock = scrollLock(top, describe);
375
413
  if (lock && fixedBy === top.document.body) fixed = false;
376
- return { rect: toBox(rect), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
414
+ return { rect: toBox(rect), content: toBox(content), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, masked: masked, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
377
415
  }`;
378
416
  /**
379
417
  * Edges of a box.
@@ -481,14 +519,15 @@ function axisScroll(start, size, viewSize, range, aim) {
481
519
  * out of a list), and `hidden` when it is not rendered at all. Being inert
482
520
  * does not change the position: inert elements are shown.
483
521
  *
484
- * @param geometry - Page-side measurements
522
+ * @param measured - Page-side measurements (a zero-size holder is placed by its children)
485
523
  * @param viewport - Top-level viewport size
486
524
  * @returns Position, with the visible share, hidden reason, clipping
487
525
  * ancestor or page scroll when relevant
488
526
  */
489
- export function classifyViewportPosition(geometry, viewport) {
490
- if (geometry.hidden)
491
- return { inViewport: 'hidden', hiddenReason: geometry.hidden };
527
+ export function classifyViewportPosition(measured, viewport) {
528
+ if (measured.hidden)
529
+ return { inViewport: 'hidden', hiddenReason: measured.hidden };
530
+ const geometry = measured.content ? { ...measured, rect: measured.content } : measured;
492
531
  const rect = edgesOf(geometry.rect);
493
532
  const screen = edgesOf({ x: 0, y: 0, ...viewport });
494
533
  const view = geometry.clip ? overlap(screen, edgesOf(geometry.clip)) : screen;
@@ -3,6 +3,19 @@
3
3
  * `dom layout`, DOM context of console messages): their text as a user sees
4
4
  * it and where they live (iframes, shadow roots).
5
5
  */
6
+ /**
7
+ * Whether a class name is shown in element labels: not empty and not a
8
+ * fragment of a declaration-like class attribute ({@link CLASS_FRAGMENT}).
9
+ *
10
+ * @param name - Class name
11
+ * @returns True to show it
12
+ */
13
+ export declare function isLabelClass(name: string): boolean;
14
+ /**
15
+ * Page-side `(node) => string[]`: the element's classes shown in labels
16
+ * ({@link isLabelClass}), read without array helpers a page may replace
17
+ */
18
+ export declare const LABEL_CLASSES_JS: string;
6
19
  /** Length of the text `dom get` shows for an element. */
7
20
  export declare const ELEMENT_TEXT_LENGTH = 500;
8
21
  /**
@@ -14,18 +27,45 @@ export declare const ELEMENT_TEXT_LENGTH = 500;
14
27
  * text is removed once, from the end.
15
28
  */
16
29
  export declare const WITHOUT_DECORATIONS_JS = "(el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n}";
30
+ /**
31
+ * Page-side check whether `innerText` misses part of what an element shows:
32
+ * it is a `<slot>` or hosts an open shadow root, or one of its descendants
33
+ * is or does. `innerText` follows neither shadow roots nor slots.
34
+ */
35
+ export declare const COMPOSED_JS = "(node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n}";
36
+ /**
37
+ * Page-side text of an element as the flat tree renders it, for elements
38
+ * `innerText` cannot read ({@link COMPOSED_JS}): a shadow host is read from
39
+ * its open shadow root (its own labels and fallback content; light children
40
+ * only where a slot shows them), and each slot is replaced by what it
41
+ * shows, its assigned nodes flattened through nested slots (or its fallback
42
+ * content when nothing is assigned). Parts without slots or shadow roots
43
+ * are read with `innerText`; elements that are not rendered and text under
44
+ * `visibility: hidden` are left out, elements that are not inline are set
45
+ * apart by line breaks, as is a `<br>`. Fields and editable regions inside
46
+ * it (inputs, textareas, selects, a contenteditable editor) are skipped, so
47
+ * what a user typed is never read; raw text keeps `innerText`'s collapsed
48
+ * whitespace. The text is cut at `limit` characters, and a
49
+ * part whose text alone passes the limit is read from its text nodes
50
+ * (`textContent`) instead of `innerText`, which would lay out all of it.
51
+ */
52
+ export declare const FLAT_TEXT_JS = "(el, limit) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea|select)$/.test(node.localName) || node.isContentEditable) return;\n const style = view.getComputedStyle(node);\n if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;\n if (node.localName === 'br') text += '\\n';\n const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';\n text += apart;\n const own = node.textContent || '';\n if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));\n else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);\n else text += typeof node.innerText === 'string' ? node.innerText : own;\n text += apart;\n };\n const visible = view.getComputedStyle(el).visibility === 'visible';\n nodesOf(el).forEach((child) => read(child, visible));\n return text.slice(0, limit);\n}";
17
53
  /**
18
54
  * Page-side text of an element as a user sees it: `innerText` for a rendered
19
55
  * element (CSS-hidden parts left out, inline elements not split apart), none
20
56
  * for an element that is not rendered, `textContent` for SVG and other
21
57
  * elements without `innerText` and for `display: contents` wrappers (no box
22
58
  * of their own, but their children are shown), and the label of an
23
- * `<option>` (which its `<select>` renders). For large containers (more
24
- * than 2000 characters of text) only the start is read, from the text nodes
25
- * whose parent is rendered, so a preview never lays out a whole page's text,
26
- * unless `full` is set. Decorations are left out ({@link WITHOUT_DECORATIONS_JS}).
59
+ * `<option>` (which its `<select>` renders). A web component, a slot, and
60
+ * an element holding either are read through the flat tree
61
+ * ({@link FLAT_TEXT_JS}): the text a component renders from its shadow
62
+ * root, slotted content in place of its slots, blocks set apart. For large
63
+ * containers (more than 2000 characters of text) only the start is read,
64
+ * from the text nodes whose parent is rendered, so a preview never lays out
65
+ * a whole page's text, unless `full` is set. Decorations are left out
66
+ * ({@link WITHOUT_DECORATIONS_JS}).
27
67
  */
28
- export declare const ELEMENT_TEXT_JS = "(el, full) => {\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const all = el.textContent || '';\n if (el.tagName === 'OPTION') return el.label;\n if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n if (!rendered(el)) {\n const boxless = el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';\n return boxless ? withoutDecorations(el, full ? all : all.slice(0, 2000)) : '';\n }\n if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let start = '';\n while (start.length < 1000 && walker.nextNode()) {\n const parent = walker.currentNode.parentElement;\n if (!parent || rendered(parent)) start += walker.currentNode.data;\n }\n return withoutDecorations(el, start);\n}";
68
+ export declare const ELEMENT_TEXT_JS = "(el, full) => {\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const flatText = (el, limit) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea|select)$/.test(node.localName) || node.isContentEditable) return;\n const style = view.getComputedStyle(node);\n if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;\n if (node.localName === 'br') text += '\\n';\n const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';\n text += apart;\n const own = node.textContent || '';\n if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));\n else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);\n else text += typeof node.innerText === 'string' ? node.innerText : own;\n text += apart;\n };\n const visible = view.getComputedStyle(el).visibility === 'visible';\n nodesOf(el).forEach((child) => read(child, visible));\n return text.slice(0, limit);\n};\n const all = el.textContent || '';\n if (el.tagName === 'OPTION') return el.label;\n if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const shown = rendered(el);\n const boxless = !shown && el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';\n if (!shown && !boxless) return '';\n if (composed(el)) return withoutDecorations(el, flatText(el, full ? Infinity : 2000));\n if (boxless) return withoutDecorations(el, full ? all : all.slice(0, 2000));\n if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let start = '';\n while (start.length < 1000 && walker.nextNode()) {\n const parent = walker.currentNode.parentElement;\n if (!parent || rendered(parent)) start += walker.currentNode.data;\n }\n return withoutDecorations(el, start);\n}";
29
69
  /** Shown instead of a secret field value (the same for every length) */
30
70
  export declare const MASKED_VALUE = "\u2022\u2022\u2022\u2022";
31
71
  /**
@@ -53,26 +93,40 @@ export declare const SENSITIVE_FIELD_JS = "(el) => {\n const autocomplete = el.
53
93
  */
54
94
  export declare const ELEMENT_STATE_JS = "(el) => {\n const isSensitive = (el) => {\n const autocomplete = el.getAttribute('autocomplete') || '';\n if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;\n if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;\n const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');\n if (/passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc/i.test(names)) return true;\n try {\n const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');\n return Boolean(security) && security !== 'none';\n } catch (e) {\n return false;\n }\n};\n const mask = (value) => (value ? '\u2022\u2022\u2022\u2022' : '');\n const guarded = (state) => {\n if (!isSensitive(el)) return state;\n const result = { ...state, sensitive: true };\n if ('value' in result) result.value = mask(result.value);\n if ('selected' in result) result.selected = mask(result.selected);\n return result;\n };\n switch (el.localName) {\n case 'input':\n if (el.type === 'hidden') return { type: 'hidden' };\n return guarded(\n /^(checkbox|radio)$/.test(el.type)\n ? { type: el.type, checked: el.checked, value: el.getAttribute('value') || '' }\n : { type: el.type, value: el.value }\n );\n case 'textarea':\n return guarded({ value: el.value });\n case 'select':\n return guarded({ selected: Array.from(el.selectedOptions || [], (option) => option.label).join(', ') });\n case 'button':\n return el.form ? { type: el.type } : {};\n default:\n return {};\n }\n}";
55
95
  /**
56
- * Page-side short description of an element: tag, id and up to two classes,
57
- * e.g. `button#save.primary.large`.
96
+ * Page-side short description of an element: tag, id and up to two classes
97
+ * ({@link LABEL_CLASSES_JS}), e.g. `button#save.primary.large`.
98
+ */
99
+ export declare const ELEMENT_DESCRIPTION_JS: string;
100
+ /**
101
+ * Page-side reason an element is disabled, or null when it is not. A native
102
+ * control (`button`, `input`, `select`, `textarea`, `fieldset`, `optgroup`,
103
+ * `option`) is disabled by its `disabled attribute` or by being `inside a
104
+ * disabled <fieldset>` (not in its first `<legend>`), an `<option>` by
105
+ * being `inside a disabled <optgroup>`; any other element
106
+ * (e.g. a custom element) when its `disabled` property is set (empty
107
+ * reason). Reads the element's own attributes rather than
108
+ * `matches(':disabled')`, which pages replace.
58
109
  */
59
- export declare const ELEMENT_DESCRIPTION_JS = "(node) => node.tagName.toLowerCase() +\n (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : '')";
110
+ export declare const DISABLED_CAUSE_JS = "(el) => {\n if (!/^(button|input|select|textarea|fieldset|optgroup|option)$/.test(el.localName)) return el.disabled ? '' : null;\n if (el.hasAttribute('disabled')) return 'disabled attribute';\n if (el.localName === 'option') {\n const group = el.parentElement;\n return group && group.localName === 'optgroup' && group.hasAttribute('disabled') ? 'inside a disabled <optgroup>' : null;\n }\n for (let node = el.parentElement; node; node = node.parentElement) {\n if (node.localName !== 'fieldset' || !node.hasAttribute('disabled')) continue;\n let legend = node.firstElementChild;\n while (legend && legend.localName !== 'legend') legend = legend.nextElementSibling;\n if (!legend || !legend.contains(el)) return 'inside a disabled <fieldset>';\n }\n return null;\n}";
60
111
  /**
61
112
  * Page-side position of an element among its parent's children with the
62
113
  * same short description ({@link ELEMENT_DESCRIPTION_JS}), e.g. `(2nd of 3)`;
63
114
  * empty when it is the only one.
64
115
  */
65
- export declare const SIBLING_POSITION_JS = "(el) => {\n const describe = (node) => node.tagName.toLowerCase() +\n (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : '');\n const parent = el.parentElement;\n if (!parent) return '';\n const own = describe(el);\n const same = Array.from(parent.children).filter((child) => describe(child) === own);\n if (same.length < 2) return '';\n const n = same.indexOf(el) + 1;\n const teen = n % 100 >= 11 && n % 100 <= 13;\n const suffix = teen ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' })[n % 10] || 'th';\n return '(' + n + suffix + ' of ' + same.length + ')';\n}";
116
+ export declare const SIBLING_POSITION_JS: string;
66
117
  /**
67
118
  * Page-side identity of an element an action hit, so the output says which
68
119
  * one it was, as one string: its short description
69
120
  * ({@link ELEMENT_DESCRIPTION_JS}) and visible text (button value for button
70
- * inputs, decorations left out), e.g. `button#add.btn "Add to cart"`. A
71
- * `<select>` is named by its label, aria-label or name, else by its selected
121
+ * inputs, decorations left out; read through the flat tree for web
122
+ * components and slots, {@link FLAT_TEXT_JS}), e.g.
123
+ * `button#add.btn "Add to cart"`. A `<select>` is named by its label
124
+ * (`aria-labelledby` or `<label>`), aria-label or name, else by its selected
72
125
  * option, e.g. `select.sort "Sort products"`. An element without visible
73
126
  * text is described by itself first: by its position among same-looking
74
127
  * siblings ({@link SIBLING_POSITION_JS}), e.g. `div.figure (2nd of 3)`, with
75
- * its aria-label, placeholder or title when it has one. Only an element
128
+ * its name when it has one: aria-label, label, placeholder, title, or the
129
+ * alt text of an image (in it). Only an element
76
130
  * without an id that is the only one of its kind is named by the nearest of
77
131
  * three ancestors that has text, e.g.
78
132
  * `input.toggle in div.view "Write report"` (rows of a list share their
@@ -80,17 +134,20 @@ export declare const SIBLING_POSITION_JS = "(el) => {\n const describe = (node)
80
134
  * of `<select>`s in it (read like `innerText`: CSS-hidden text is left out of
81
135
  * a rendered ancestor; at most 500 text nodes; selects in shadow roots are
82
136
  * not looked into), and never past an editable ancestor, whose text may be
83
- * typed input; otherwise its aria-label, placeholder or title is used. Texts
137
+ * typed input; otherwise its name is used. Texts
84
138
  * are cut at 40 characters. Contenteditable elements count as controls: what
85
139
  * was typed into them is never echoed (nor their ancestors' text).
86
140
  */
87
- export declare const ELEMENT_IDENTITY_JS = "(el) => {\n const describe = (node) => node.tagName.toLowerCase() +\n (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : '');\n const siblingPosition = (el) => {\n const describe = (node) => node.tagName.toLowerCase() +\n (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : '');\n const parent = el.parentElement;\n if (!parent) return '';\n const own = describe(el);\n const same = Array.from(parent.children).filter((child) => describe(child) === own);\n if (same.length < 2) return '';\n const n = same.indexOf(el) + 1;\n const teen = n % 100 >= 11 && n % 100 <= 13;\n const suffix = teen ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' })[n % 10] || 'th';\n return '(' + n + suffix + ' of ' + same.length + ')';\n};\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const clean = (text) => (text || '').replace(/\\s+/g, ' ').trim();\n const cut = (text) => {\n const characters = Array.from(text);\n return characters.length > 40 ? characters.slice(0, 40).join('') + '\u2026' : text;\n };\n const isControl = (node) => /^(input|select|textarea)$/.test(node.localName) || node.isContentEditable;\n const shownText = (node) =>\n isControl(node) ? '' : clean(withoutDecorations(node, typeof node.innerText === 'string' ? node.innerText : node.textContent));\n const buttonValue = (node) => (node.localName === 'input' && /^(submit|button|reset)$/i.test(node.type) ? clean(node.value) : '');\n const attributeText = (node) =>\n clean(node.getAttribute('aria-label')) || clean(node.getAttribute('placeholder')) || clean(node.getAttribute('title'));\n const selectName = (node) =>\n clean(node.labels && node.labels[0] && node.labels[0].innerText) ||\n clean(node.getAttribute('aria-label')) ||\n clean(node.getAttribute('name')) ||\n clean(node.selectedOptions && node.selectedOptions[0] && node.selectedOptions[0].label);\n const shown = (node, options) => !node.checkVisibility || node.checkVisibility(options);\n const textOutsideSelects = (node) => {\n if (!node.querySelector('select')) return shownText(node);\n const hiddenLeftOut = shown(node);\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_TEXT);\n let text = '';\n for (let visited = 0; visited < 500 && text.length < 200 && walker.nextNode(); visited++) {\n const parent = walker.currentNode.parentElement;\n if (!parent || parent.closest('select, textarea')) continue;\n if (!hiddenLeftOut || shown(parent, { visibilityProperty: true })) text += ' ' + walker.currentNode.data;\n }\n return clean(text);\n };\n const quoted = (text) => (text ? ' \"' + cut(text) + '\"' : '');\n const visible = shownText(el) || buttonValue(el) || (el.localName === 'select' ? selectName(el) : '');\n if (visible) return describe(el) + quoted(visible);\n const attribute = attributeText(el);\n const position = siblingPosition(el);\n if (position) return describe(el) + ' ' + position + quoted(attribute);\n let ancestor = el.id || el.isContentEditable ? null : el.parentElement;\n for (let depth = 0; ancestor && depth < 3; depth++, ancestor = ancestor.parentElement) {\n if (isControl(ancestor)) break;\n const text = textOutsideSelects(ancestor);\n if (text) return describe(el) + ' in ' + describe(ancestor) + quoted(text);\n }\n return describe(el) + quoted(attribute);\n}";
141
+ export declare const ELEMENT_IDENTITY_JS: string;
88
142
  /**
89
143
  * Page-side location of an element: the iframes it is in (outermost first)
90
- * and the shadow root holding it, e.g. `iframe#pay > shadow root of <x-card>`;
91
- * empty for the main document.
144
+ * and the shadow root holding it, with the host's first class and the text
145
+ * a user sees of it ({@link ELEMENT_TEXT_JS}, cut at 30 characters with
146
+ * `…`), e.g. `iframe#pay > shadow root of <sl-button.primary "Save">`;
147
+ * empty for the main document. Each host's text is read once per script
148
+ * run, as many matches share a host.
92
149
  */
93
- export declare const ELEMENT_CONTEXT_JS = "(el) => {\n const describe = (node) => node.tagName.toLowerCase() + (node.id ? '#' + node.id : '');\n const parts = [];\n for (let doc = el.ownerDocument; doc && doc.defaultView && doc.defaultView.frameElement; ) {\n const frame = doc.defaultView.frameElement;\n parts.unshift(describe(frame));\n doc = frame.ownerDocument;\n }\n const root = el.getRootNode();\n if (root.host) parts.push('shadow root of <' + describe(root.host) + '>');\n return parts.join(' > ');\n}";
150
+ export declare const ELEMENT_CONTEXT_JS: string;
94
151
  /**
95
152
  * Short text preview of an element's text: whitespace collapsed, cut on a
96
153
  * whole character (an emoji is never split, which would make JSON invalid).