@olimsaidov/icdp 0.2.0 → 0.3.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.
@@ -1,2186 +0,0 @@
1
- import { roles } from "aria-query";
2
- import { computeAccessibleDescription, getRole } from "dom-accessibility-api";
3
- //#region src/frame/ax-tree.ts
4
- const MOJOM_ROLE_ORDINALS = {
5
- None: 0,
6
- Abbr: 1,
7
- Alert: 2,
8
- AlertDialog: 3,
9
- Application: 4,
10
- Article: 5,
11
- Audio: 6,
12
- Banner: 7,
13
- Blockquote: 8,
14
- Button: 9,
15
- Canvas: 10,
16
- Caption: 11,
17
- Caret: 12,
18
- Cell: 13,
19
- CheckBox: 14,
20
- Client: 15,
21
- Code: 16,
22
- ColorWell: 17,
23
- Column: 18,
24
- ColumnHeader: 19,
25
- ComboBoxGrouping: 20,
26
- ComboBoxMenuButton: 21,
27
- Complementary: 22,
28
- Comment: 23,
29
- ContentDeletion: 24,
30
- ContentInsertion: 25,
31
- ContentInfo: 26,
32
- Date: 27,
33
- DateTime: 28,
34
- Definition: 29,
35
- DescriptionList: 30,
36
- DescriptionListDetailDeprecated: 31,
37
- DescriptionListTermDeprecated: 32,
38
- Desktop: 33,
39
- Details: 34,
40
- Dialog: 35,
41
- DirectoryDeprecated: 36,
42
- DisclosureTriangle: 37,
43
- DocAbstract: 38,
44
- DocAcknowledgments: 39,
45
- DocAfterword: 40,
46
- DocAppendix: 41,
47
- DocBackLink: 42,
48
- DocBiblioEntry: 43,
49
- DocBibliography: 44,
50
- DocBiblioRef: 45,
51
- DocChapter: 46,
52
- DocColophon: 47,
53
- DocConclusion: 48,
54
- DocCover: 49,
55
- DocCredit: 50,
56
- DocCredits: 51,
57
- DocDedication: 52,
58
- DocEndnote: 53,
59
- DocEndnotes: 54,
60
- DocEpigraph: 55,
61
- DocEpilogue: 56,
62
- DocErrata: 57,
63
- DocExample: 58,
64
- DocFootnote: 59,
65
- DocForeword: 60,
66
- DocGlossary: 61,
67
- DocGlossRef: 62,
68
- DocIndex: 63,
69
- DocIntroduction: 64,
70
- DocNoteRef: 65,
71
- DocNotice: 66,
72
- DocPageBreak: 67,
73
- DocPageFooter: 68,
74
- DocPageHeader: 69,
75
- DocPageList: 70,
76
- DocPart: 71,
77
- DocPreface: 72,
78
- DocPrologue: 73,
79
- DocPullquote: 74,
80
- DocQna: 75,
81
- DocSubtitle: 76,
82
- DocTip: 77,
83
- DocToc: 78,
84
- Document: 79,
85
- EmbeddedObject: 80,
86
- Emphasis: 81,
87
- Feed: 82,
88
- Figcaption: 83,
89
- Figure: 84,
90
- Footer: 85,
91
- SectionFooter: 86,
92
- Form: 87,
93
- GenericContainer: 88,
94
- GraphicsDocument: 89,
95
- GraphicsObject: 90,
96
- GraphicsSymbol: 91,
97
- Grid: 92,
98
- Group: 93,
99
- Header: 94,
100
- SectionHeader: 95,
101
- Heading: 96,
102
- Iframe: 97,
103
- IframePresentational: 98,
104
- Image: 99,
105
- ImeCandidate: 100,
106
- InlineTextBox: 101,
107
- InputTime: 102,
108
- Keyboard: 103,
109
- LabelText: 104,
110
- LayoutTable: 105,
111
- LayoutTableCell: 106,
112
- LayoutTableRow: 107,
113
- Legend: 108,
114
- LineBreak: 109,
115
- Link: 110,
116
- List: 111,
117
- ListBox: 112,
118
- ListBoxOption: 113,
119
- ListGrid: 114,
120
- ListItem: 115,
121
- ListMarker: 116,
122
- Log: 117,
123
- Main: 118,
124
- Mark: 119,
125
- Marquee: 120,
126
- Math: 121,
127
- Menu: 122,
128
- MenuBar: 123,
129
- MenuItem: 124,
130
- MenuItemCheckBox: 125,
131
- MenuItemRadio: 126,
132
- MenuListOption: 127,
133
- MenuListPopup: 128,
134
- Meter: 129,
135
- Navigation: 130,
136
- Note: 131,
137
- Pane: 132,
138
- Paragraph: 133,
139
- PdfActionableHighlight: 134,
140
- PdfRoot: 135,
141
- PluginObject: 136,
142
- PopUpButton: 137,
143
- PortalDeprecated: 138,
144
- PreDeprecated: 139,
145
- ProgressIndicator: 140,
146
- RadioButton: 141,
147
- RadioGroup: 142,
148
- Region: 143,
149
- RootWebArea: 144,
150
- Row: 145,
151
- RowGroup: 146,
152
- RowHeader: 147,
153
- Ruby: 148,
154
- RubyAnnotation: 149,
155
- ScrollBar: 150,
156
- ScrollView: 151,
157
- Search: 152,
158
- SearchBox: 153,
159
- Section: 154,
160
- Slider: 155,
161
- SpinButton: 156,
162
- Splitter: 157,
163
- StaticText: 158,
164
- Status: 159,
165
- Strong: 160,
166
- Suggestion: 161,
167
- SvgRoot: 162,
168
- Switch: 163,
169
- Tab: 164,
170
- TabList: 165,
171
- TabPanel: 166,
172
- Table: 167,
173
- TableHeaderContainer: 168,
174
- Term: 169,
175
- TextField: 170,
176
- TextFieldWithComboBox: 171,
177
- Time: 172,
178
- Timer: 173,
179
- TitleBar: 174,
180
- ToggleButton: 175,
181
- Toolbar: 176,
182
- Tooltip: 177,
183
- Tree: 178,
184
- TreeGrid: 179,
185
- TreeItem: 180,
186
- Unknown: 181,
187
- Video: 182,
188
- WebView: 183,
189
- Window: 184,
190
- Subscript: 185,
191
- Superscript: 186,
192
- MathMLMath: 187,
193
- MathMLFraction: 188,
194
- MathMLIdentifier: 189,
195
- MathMLMultiscripts: 190,
196
- MathMLNoneScript: 191,
197
- MathMLNumber: 192,
198
- MathMLOperator: 193,
199
- MathMLOver: 194,
200
- MathMLPrescriptDelimiter: 195,
201
- MathMLRoot: 196,
202
- MathMLRow: 197,
203
- MathMLSquareRoot: 198,
204
- MathMLStringLiteral: 199,
205
- MathMLSub: 200,
206
- MathMLSubSup: 201,
207
- MathMLSup: 202,
208
- MathMLTable: 203,
209
- MathMLTableCell: 204,
210
- MathMLTableRow: 205,
211
- MathMLText: 206,
212
- MathMLUnder: 207,
213
- MathMLUnderOver: 208,
214
- ComboBoxSelect: 209,
215
- DisclosureTriangleGrouped: 210,
216
- SectionWithoutName: 211,
217
- GridCell: 212,
218
- MenuItemSeparator: 213
219
- };
220
- const ARIA_TO_MOJOM = {
221
- alert: "Alert",
222
- alertdialog: "AlertDialog",
223
- application: "Application",
224
- article: "Article",
225
- banner: "Banner",
226
- blockquote: "Blockquote",
227
- button: "Button",
228
- caption: "Caption",
229
- cell: "Cell",
230
- checkbox: "CheckBox",
231
- code: "Code",
232
- columnheader: "ColumnHeader",
233
- combobox: "ComboBoxGrouping",
234
- comment: "Comment",
235
- complementary: "Complementary",
236
- contentinfo: "ContentInfo",
237
- definition: "Definition",
238
- deletion: "ContentDeletion",
239
- dialog: "Dialog",
240
- directory: "List",
241
- "doc-abstract": "DocAbstract",
242
- "doc-acknowledgments": "DocAcknowledgments",
243
- "doc-afterword": "DocAfterword",
244
- "doc-appendix": "DocAppendix",
245
- "doc-backlink": "DocBackLink",
246
- "doc-biblioentry": "DocBiblioEntry",
247
- "doc-bibliography": "DocBibliography",
248
- "doc-biblioref": "DocBiblioRef",
249
- "doc-chapter": "DocChapter",
250
- "doc-colophon": "DocColophon",
251
- "doc-conclusion": "DocConclusion",
252
- "doc-cover": "DocCover",
253
- "doc-credit": "DocCredit",
254
- "doc-credits": "DocCredits",
255
- "doc-dedication": "DocDedication",
256
- "doc-endnote": "DocEndnote",
257
- "doc-endnotes": "DocEndnotes",
258
- "doc-epigraph": "DocEpigraph",
259
- "doc-epilogue": "DocEpilogue",
260
- "doc-errata": "DocErrata",
261
- "doc-example": "DocExample",
262
- "doc-footnote": "DocFootnote",
263
- "doc-foreword": "DocForeword",
264
- "doc-glossary": "DocGlossary",
265
- "doc-glossref": "DocGlossRef",
266
- "doc-index": "DocIndex",
267
- "doc-introduction": "DocIntroduction",
268
- "doc-noteref": "DocNoteRef",
269
- "doc-notice": "DocNotice",
270
- "doc-pagebreak": "DocPageBreak",
271
- "doc-pagefooter": "DocPageFooter",
272
- "doc-pageheader": "DocPageHeader",
273
- "doc-pagelist": "DocPageList",
274
- "doc-part": "DocPart",
275
- "doc-preface": "DocPreface",
276
- "doc-prologue": "DocPrologue",
277
- "doc-pullquote": "DocPullquote",
278
- "doc-qna": "DocQna",
279
- "doc-subtitle": "DocSubtitle",
280
- "doc-tip": "DocTip",
281
- "doc-toc": "DocToc",
282
- document: "Document",
283
- emphasis: "Emphasis",
284
- feed: "Feed",
285
- figure: "Figure",
286
- form: "GenericContainer",
287
- "graphics-document": "GraphicsDocument",
288
- "graphics-object": "GraphicsObject",
289
- "graphics-symbol": "GraphicsSymbol",
290
- grid: "Grid",
291
- gridcell: "GridCell",
292
- group: "Group",
293
- heading: "Heading",
294
- image: "Image",
295
- img: "Image",
296
- insertion: "ContentInsertion",
297
- link: "Link",
298
- list: "List",
299
- listbox: "ListBox",
300
- listitem: "ListItem",
301
- log: "Log",
302
- main: "Main",
303
- mark: "Mark",
304
- marquee: "Marquee",
305
- math: "Math",
306
- menu: "Menu",
307
- menubar: "MenuBar",
308
- menuitem: "MenuItem",
309
- menuitemcheckbox: "MenuItemCheckBox",
310
- menuitemradio: "MenuItemRadio",
311
- meter: "Meter",
312
- navigation: "Navigation",
313
- none: "None",
314
- note: "Note",
315
- option: "ListBoxOption",
316
- paragraph: "Paragraph",
317
- presentation: "None",
318
- progressbar: "ProgressIndicator",
319
- radio: "RadioButton",
320
- radiogroup: "RadioGroup",
321
- region: "Region",
322
- row: "Row",
323
- rowgroup: "RowGroup",
324
- rowheader: "RowHeader",
325
- scrollbar: "ScrollBar",
326
- search: "Search",
327
- searchbox: "SearchBox",
328
- section: "Section",
329
- sectionfooter: "SectionFooter",
330
- sectionheader: "SectionHeader",
331
- separator: "Splitter",
332
- slider: "Slider",
333
- spinbutton: "SpinButton",
334
- status: "Status",
335
- strong: "Strong",
336
- subscript: "Subscript",
337
- suggestion: "Suggestion",
338
- superscript: "Superscript",
339
- switch: "Switch",
340
- tab: "Tab",
341
- table: "Table",
342
- tablist: "TabList",
343
- tabpanel: "TabPanel",
344
- term: "Term",
345
- textbox: "TextField",
346
- time: "Time",
347
- timer: "Timer",
348
- toolbar: "Toolbar",
349
- tooltip: "Tooltip",
350
- tree: "Tree",
351
- treegrid: "TreeGrid",
352
- treeitem: "TreeItem",
353
- window: "Window"
354
- };
355
- const NAME_PROHIBITED_ROLES = new Set([
356
- "caption",
357
- "code",
358
- "definition",
359
- "deletion",
360
- "emphasis",
361
- "insertion",
362
- "mark",
363
- "none",
364
- "paragraph",
365
- "strong",
366
- "subscript",
367
- "suggestion",
368
- "superscript",
369
- "term",
370
- "time",
371
- "generic"
372
- ]);
373
- const nodeToAXId = /* @__PURE__ */ new WeakMap();
374
- let nextAXId = 1;
375
- function createDomRegistry() {
376
- const nodeToBackendId = /* @__PURE__ */ new WeakMap();
377
- const backendIdToNode = /* @__PURE__ */ new Map();
378
- let nextBackendId = 1;
379
- return {
380
- backendIdFor(node) {
381
- const existing = nodeToBackendId.get(node);
382
- if (existing) return existing;
383
- const id = nextBackendId++;
384
- nodeToBackendId.set(node, id);
385
- backendIdToNode.set(id, node);
386
- return id;
387
- },
388
- nodeForBackendId(id) {
389
- return backendIdToNode.get(id);
390
- }
391
- };
392
- }
393
- function ax(type, value) {
394
- return value === void 0 ? { type: "valueUndefined" } : {
395
- type,
396
- value
397
- };
398
- }
399
- function axIdFor(node) {
400
- const existing = nodeToAXId.get(node);
401
- if (existing) return existing;
402
- const id = String(nextAXId++);
403
- nodeToAXId.set(node, id);
404
- return id;
405
- }
406
- function isElement(node) {
407
- return node.nodeType === Node.ELEMENT_NODE;
408
- }
409
- function isText(node) {
410
- return node.nodeType === Node.TEXT_NODE;
411
- }
412
- function normalizeText(value) {
413
- return (value || "").replace(/\s+/g, " ").trim();
414
- }
415
- const INTERNAL_WIRE_ROLES = new Set([
416
- "StaticText",
417
- "RootWebArea",
418
- "ListMarker",
419
- "DisclosureTriangle",
420
- "LabelText",
421
- "Iframe",
422
- "Canvas",
423
- "MenuListPopup",
424
- "MathMLMath",
425
- "MathMLIdentifier",
426
- "MathMLOperator",
427
- "MathMLNumber"
428
- ]);
429
- function roleNameValue(role) {
430
- return ax(INTERNAL_WIRE_ROLES.has(role) ? "internalRole" : "role", role);
431
- }
432
- function chromeRoleValue(mojomName) {
433
- return ax("internalRole", MOJOM_ROLE_ORDINALS[mojomName] ?? 0);
434
- }
435
- function explicitRole(el) {
436
- const attr = el.getAttribute("role");
437
- if (!attr) return null;
438
- for (const token of attr.trim().split(/\s+/)) {
439
- const role = token === "image" ? "img" : token;
440
- if (roles.has(role)) return role;
441
- }
442
- return null;
443
- }
444
- const TEXT_ENTRY_INPUTS = new Set([
445
- "",
446
- "text",
447
- "email",
448
- "url",
449
- "tel",
450
- "password",
451
- "search"
452
- ]);
453
- function inputRole(el) {
454
- const type = (el.getAttribute("type") || "").toLowerCase();
455
- if (type === "checkbox") return {
456
- wire: "checkbox",
457
- mojom: "CheckBox"
458
- };
459
- if (type === "radio") return {
460
- wire: "radio",
461
- mojom: "RadioButton"
462
- };
463
- if (type === "range") return {
464
- wire: "slider",
465
- mojom: "Slider"
466
- };
467
- if (type === "number") return {
468
- wire: "spinbutton",
469
- mojom: "SpinButton"
470
- };
471
- if (type === "search") return {
472
- wire: "searchbox",
473
- mojom: "SearchBox"
474
- };
475
- if (type === "color") return {
476
- wire: "ColorWell",
477
- mojom: "ColorWell"
478
- };
479
- if (type === "date") return {
480
- wire: "Date",
481
- mojom: "Date"
482
- };
483
- if ([
484
- "datetime-local",
485
- "month",
486
- "week"
487
- ].includes(type)) return {
488
- wire: "DateTime",
489
- mojom: "DateTime"
490
- };
491
- if (type === "time") return {
492
- wire: "InputTime",
493
- mojom: "InputTime"
494
- };
495
- if ([
496
- "button",
497
- "submit",
498
- "reset",
499
- "image"
500
- ].includes(type)) return {
501
- wire: "button",
502
- mojom: "Button"
503
- };
504
- return {
505
- wire: "textbox",
506
- mojom: "TextField"
507
- };
508
- }
509
- /** The element's native (non-ARIA) role, Chromium-aligned. */
510
- function nativeRole(el) {
511
- const tag = el.localName;
512
- const none = {
513
- wire: null,
514
- mojom: "GenericContainer"
515
- };
516
- if (tag === "html" || tag === "body") return none;
517
- if (tag === "math") return {
518
- wire: "MathMLMath",
519
- mojom: "MathMLMath"
520
- };
521
- if (tag === "mi") return {
522
- wire: "MathMLIdentifier",
523
- mojom: "MathMLMath"
524
- };
525
- if (tag === "mo") return {
526
- wire: "MathMLOperator",
527
- mojom: "MathMLMath"
528
- };
529
- if (tag === "mn") return {
530
- wire: "MathMLNumber",
531
- mojom: "MathMLMath"
532
- };
533
- if (tag === "summary") return el.parentElement?.localName === "details" ? {
534
- wire: "DisclosureTriangle",
535
- mojom: "DisclosureTriangle"
536
- } : none;
537
- if (tag === "label") return {
538
- wire: "LabelText",
539
- mojom: "LabelText"
540
- };
541
- if (tag === "iframe" || tag === "frame") return {
542
- wire: "Iframe",
543
- mojom: "Iframe"
544
- };
545
- if (tag === "svg") return {
546
- wire: "image",
547
- mojom: "Image"
548
- };
549
- if (tag === "img") {
550
- if (el.getAttribute("alt") === "" && !el.hasAttribute("title") && !el.hasAttribute("aria-label") && !el.hasAttribute("aria-labelledby")) return none;
551
- return {
552
- wire: "image",
553
- mojom: "Image"
554
- };
555
- }
556
- if (tag === "button") return {
557
- wire: "button",
558
- mojom: "Button"
559
- };
560
- if (tag === "a" && el.hasAttribute("href")) return {
561
- wire: "link",
562
- mojom: "Link"
563
- };
564
- if (tag === "textarea") return {
565
- wire: "textbox",
566
- mojom: "TextField"
567
- };
568
- if (el instanceof HTMLInputElement) return inputRole(el);
569
- if (tag === "select") return el.hasAttribute("multiple") ? {
570
- wire: "listbox",
571
- mojom: "ListBox"
572
- } : {
573
- wire: "combobox",
574
- mojom: "ComboBoxSelect"
575
- };
576
- if (tag === "option") return {
577
- wire: "option",
578
- mojom: "MenuListOption"
579
- };
580
- if (tag === "optgroup") return {
581
- wire: "group",
582
- mojom: "Group"
583
- };
584
- if (tag === "p") return {
585
- wire: "paragraph",
586
- mojom: "Paragraph"
587
- };
588
- if (/^h[1-6]$/.test(tag)) return {
589
- wire: "heading",
590
- mojom: "Heading"
591
- };
592
- if (tag === "ul" || tag === "ol") return {
593
- wire: "list",
594
- mojom: "List"
595
- };
596
- if (tag === "li") return {
597
- wire: "listitem",
598
- mojom: "ListItem"
599
- };
600
- if (tag === "table") return {
601
- wire: "table",
602
- mojom: "Table"
603
- };
604
- if (tag === "thead" || tag === "tfoot") return {
605
- wire: "rowgroup",
606
- mojom: "RowGroup"
607
- };
608
- if (tag === "tr") return {
609
- wire: "row",
610
- mojom: "Row"
611
- };
612
- if (tag === "td") return {
613
- wire: "cell",
614
- mojom: "Cell"
615
- };
616
- if (tag === "th") return {
617
- wire: "columnheader",
618
- mojom: "ColumnHeader"
619
- };
620
- if (tag === "nav") return {
621
- wire: "navigation",
622
- mojom: "Navigation"
623
- };
624
- if (tag === "main") return {
625
- wire: "main",
626
- mojom: "Main"
627
- };
628
- if (tag === "dialog") return {
629
- wire: "dialog",
630
- mojom: "Dialog"
631
- };
632
- if (tag === "canvas") return {
633
- wire: "Canvas",
634
- mojom: "Canvas"
635
- };
636
- const computed = getRole(el);
637
- if (computed && computed !== "generic" && roles.has(computed)) return {
638
- wire: computed,
639
- mojom: ARIA_TO_MOJOM[computed] ?? "Unknown"
640
- };
641
- return none;
642
- }
643
- function roleInfoOf(el) {
644
- const override = el.__agentAX?.role;
645
- if (typeof override === "string" && override) return {
646
- wire: override,
647
- mojom: ARIA_TO_MOJOM[override] ?? "Unknown"
648
- };
649
- const explicit = explicitRole(el);
650
- if (explicit && explicit !== "none" && explicit !== "presentation") return {
651
- wire: explicit === "img" ? "image" : explicit,
652
- mojom: ARIA_TO_MOJOM[explicit] ?? "Unknown"
653
- };
654
- return nativeRole(el);
655
- }
656
- const SKIP_TAGS = new Set([
657
- "head",
658
- "style",
659
- "script",
660
- "noscript",
661
- "template",
662
- "meta",
663
- "link",
664
- "base",
665
- "title",
666
- "datalist",
667
- "param",
668
- "track",
669
- "source",
670
- "col",
671
- "colgroup",
672
- "br"
673
- ]);
674
- function isSkipped(el) {
675
- if (SKIP_TAGS.has(el.localName)) return true;
676
- if (el instanceof HTMLInputElement && el.type === "hidden") return true;
677
- return false;
678
- }
679
- function isUnrendered(el) {
680
- if (el.hidden) return true;
681
- if (el.localName === "dialog" && !el.hasAttribute("open")) return true;
682
- const parent = el.parentElement;
683
- if (parent?.localName === "details" && !parent.hasAttribute("open") && el.localName !== "summary") return true;
684
- return getComputedStyle(el).display === "none";
685
- }
686
- function isInvisible(el) {
687
- const visibility = getComputedStyle(el).visibility;
688
- return visibility === "hidden" || visibility === "collapse";
689
- }
690
- function isElHiddenForText(el) {
691
- return el.hidden || el.getAttribute("aria-hidden") === "true" || getComputedStyle(el).display === "none" || isInvisible(el);
692
- }
693
- function hasHiddenAncestorOrSelf(el) {
694
- for (let cur = el; cur; cur = cur.parentElement) if (isElHiddenForText(cur)) return true;
695
- return false;
696
- }
697
- function isInlineLevel(el) {
698
- const display = getComputedStyle(el).display;
699
- if (display) return display.startsWith("inline") || display === "contents";
700
- return false;
701
- }
702
- function contentEditable(el) {
703
- const value = el.getAttribute("contenteditable");
704
- return value === "" || value === "true" || value === "plaintext-only";
705
- }
706
- function nativeDisabled(el) {
707
- return "disabled" in el && Boolean(el.disabled);
708
- }
709
- function isFocusable(el) {
710
- if (nativeDisabled(el)) return false;
711
- if (el.tabIndex >= 0) return true;
712
- if (el.localName === "a" && el.hasAttribute("href")) return true;
713
- if (el.localName === "dialog" && el.hasAttribute("open")) return true;
714
- if (contentEditable(el)) return true;
715
- return [
716
- "button",
717
- "input",
718
- "select",
719
- "textarea",
720
- "option"
721
- ].includes(el.localName);
722
- }
723
- function isPresentational(el) {
724
- const attr = el.getAttribute("role");
725
- if (!attr) return false;
726
- let presentational = false;
727
- for (const token of attr.trim().split(/\s+/)) {
728
- if (token === "none" || token === "presentation") {
729
- presentational = true;
730
- break;
731
- }
732
- if (roles.has(token === "image" ? "img" : token)) return false;
733
- }
734
- if (!presentational) return false;
735
- if (el.hasAttribute("aria-label") || el.hasAttribute("aria-labelledby")) return false;
736
- return !isFocusable(el);
737
- }
738
- const REQUIRED_OWNED = {
739
- ul: ["li"],
740
- ol: ["li"],
741
- menu: ["li"],
742
- table: [
743
- "caption",
744
- "thead",
745
- "tbody",
746
- "tfoot",
747
- "tr"
748
- ],
749
- thead: ["tr"],
750
- tbody: ["tr"],
751
- tfoot: ["tr"],
752
- tr: ["td", "th"]
753
- };
754
- function inheritsPresentation(parent, child) {
755
- return REQUIRED_OWNED[parent.localName]?.includes(child.localName) ?? false;
756
- }
757
- function ignoredReason(name) {
758
- return {
759
- name,
760
- value: ax("boolean", true)
761
- };
762
- }
763
- /** An ignored reason carrying a relatedNodes idref to the offending element
764
- * (Chromium's CreateRelatedNodeListValue). */
765
- function relatedReason(name, related, registry) {
766
- const node = { backendDOMNodeId: registry.backendIdFor(related) };
767
- const id = related.getAttribute("id");
768
- if (id) node.idref = id;
769
- return {
770
- name,
771
- value: {
772
- type: "idref",
773
- relatedNodes: [node]
774
- }
775
- };
776
- }
777
- const modalDialogs = /* @__PURE__ */ new WeakSet();
778
- (() => {
779
- const proto = globalThis.HTMLDialogElement?.prototype;
780
- if (!proto) return;
781
- const showModal = proto.showModal;
782
- if (showModal && showModal.__axPatched) return;
783
- proto.showModal = function(...args) {
784
- modalDialogs.add(this);
785
- if (showModal) return showModal.apply(this, args);
786
- this.setAttribute("open", "");
787
- };
788
- proto.showModal.__axPatched = true;
789
- const close = proto.close;
790
- proto.close = function(...args) {
791
- modalDialogs.delete(this);
792
- if (close) return close.apply(this, args);
793
- this.removeAttribute("open");
794
- };
795
- })();
796
- /** The open modal dialog blocking the rest of the document, if any. */
797
- function openModalDialog(doc) {
798
- for (const dialog of Array.from(doc.querySelectorAll("dialog[open]"))) {
799
- try {
800
- if (dialog.matches(":modal")) return dialog;
801
- } catch {}
802
- if (modalDialogs.has(dialog)) return dialog;
803
- }
804
- return null;
805
- }
806
- /** ids referenced by any label/description relation — hidden elements so
807
- * referenced stay in the AX tree (Blink's IsUsedForLabelOrDescription). */
808
- function labelReferencedIds(doc) {
809
- const ids = /* @__PURE__ */ new Set();
810
- for (const attr of [
811
- "aria-labelledby",
812
- "aria-labeledby",
813
- "aria-describedby",
814
- "aria-owns"
815
- ]) for (const el of Array.from(doc.querySelectorAll(`[${attr}]`))) for (const id of (el.getAttribute(attr) || "").trim().split(/\s+/)) if (id) ids.add(id);
816
- return ids;
817
- }
818
- function buildTree(options) {
819
- const tree = {
820
- root: void 0,
821
- byNode: /* @__PURE__ */ new Map(),
822
- byId: /* @__PURE__ */ new Map(),
823
- options,
824
- labelReferenced: labelReferencedIds(options.document),
825
- modal: openModalDialog(options.document)
826
- };
827
- const root = {
828
- id: axIdFor(options.document),
829
- node: options.document,
830
- wireRole: "RootWebArea",
831
- mojom: "RootWebArea",
832
- ignored: false,
833
- included: true,
834
- children: [],
835
- isRoot: true
836
- };
837
- tree.root = root;
838
- register(tree, root);
839
- const html = options.document.documentElement;
840
- if (html) root.children = walkElement(tree, html, root, {});
841
- return tree;
842
- }
843
- function register(tree, obj) {
844
- tree.byNode.set(obj.node, obj);
845
- tree.byId.set(obj.id, obj);
846
- }
847
- function makeObj(tree, node, partial) {
848
- const obj = {
849
- id: axIdFor(node),
850
- node,
851
- children: [],
852
- ...partial
853
- };
854
- register(tree, obj);
855
- return obj;
856
- }
857
- /** Whether a hidden (unrendered / invisible) element stays in the AX tree. */
858
- function hiddenButIncluded(tree, el) {
859
- const id = el.getAttribute("id");
860
- if (id && tree.labelReferenced.has(id)) return true;
861
- if (el.hasAttribute("lang")) return true;
862
- if (el.localName === "label") return true;
863
- return [
864
- "table",
865
- "tbody",
866
- "thead",
867
- "tfoot",
868
- "tr",
869
- "td",
870
- "th"
871
- ].includes(el.localName);
872
- }
873
- /** Whether an ignored-but-rendered element stays in the AX tree. Excluded
874
- * nodes hoist their children to the nearest included ancestor and remain
875
- * reachable only by direct inspection (Blink's IsIgnoredButIncludedInTree). */
876
- function renderedIgnoredIncluded(tree, el) {
877
- if (el.localName === "html" || el.localName === "body") return true;
878
- const id = el.getAttribute("id");
879
- if (id && tree.labelReferenced.has(id)) return true;
880
- if (el.hasAttribute("lang")) return true;
881
- if ([
882
- "table",
883
- "tbody",
884
- "thead",
885
- "tfoot",
886
- "tr",
887
- "td",
888
- "th"
889
- ].includes(el.localName)) return true;
890
- if (el.localName === "label") return true;
891
- if (el.parentElement?.localName === "label" && el.localName !== "span") return true;
892
- return false;
893
- }
894
- function composedChildren(node) {
895
- if (isElement(node)) {
896
- const shadow = node.shadowRoot;
897
- if (shadow) return Array.from(shadow.childNodes);
898
- if (node instanceof HTMLSlotElement) {
899
- const assigned = node.assignedNodes({ flatten: true });
900
- if (assigned.length) return assigned;
901
- }
902
- if (node.localName === "table") {
903
- const children = Array.from(node.childNodes);
904
- const sections = new Set([
905
- "thead",
906
- "tbody",
907
- "tfoot"
908
- ]);
909
- const section = (name) => children.filter((child) => isElement(child) && child.localName === name);
910
- return [
911
- ...children.filter((child) => !isElement(child) || !sections.has(child.localName)),
912
- ...section("thead"),
913
- ...section("tbody"),
914
- ...section("tfoot")
915
- ];
916
- }
917
- }
918
- return Array.from(node.childNodes);
919
- }
920
- function numberAttr(el, name) {
921
- const raw = el.getAttribute(name);
922
- if (raw == null || raw === "") return void 0;
923
- const parsed = Number(raw);
924
- return Number.isFinite(parsed) ? parsed : void 0;
925
- }
926
- function listMarkerText(el) {
927
- if (el.localName !== "li" || getComputedStyle(el).display !== "list-item") return void 0;
928
- const list = el.parentElement;
929
- if (list?.localName !== "ol") return void 0;
930
- const siblings = Array.from(list.children).filter((child) => child.localName === "li" && getComputedStyle(child).display === "list-item");
931
- return `${(numberAttr(list, "start") ?? 1) + Math.max(0, siblings.indexOf(el))}. `;
932
- }
933
- /** Boundary-preserving StaticText value: internal whitespace collapses; a
934
- * leading/trailing space survives only next to rendered inline siblings
935
- * (approximates Blink's layout-driven text trimming). */
936
- function staticTextValue(node) {
937
- const collapsed = (node.nodeValue || "").replace(/\s+/g, " ");
938
- if (!collapsed) return "";
939
- const inlineNeighbor = (dir) => {
940
- for (let cur = node[dir]; cur; cur = cur[dir]) {
941
- if (isText(cur)) {
942
- if (normalizeText(cur.nodeValue)) return true;
943
- continue;
944
- }
945
- if (!isElement(cur)) continue;
946
- if (isUnrendered(cur)) return false;
947
- return isInlineLevel(cur) || [
948
- "img",
949
- "svg",
950
- "canvas"
951
- ].includes(cur.localName);
952
- }
953
- return false;
954
- };
955
- let text = collapsed;
956
- if (text.startsWith(" ") && !inlineNeighbor("previousSibling")) text = text.slice(1);
957
- if (text.endsWith(" ") && !inlineNeighbor("nextSibling")) text = text.slice(0, -1);
958
- return text === " " ? "" : text;
959
- }
960
- /** Walk one element; returns the AXObjs to splice into the parent's children. */
961
- function walkElement(tree, el, parent, ctx) {
962
- if (isSkipped(el)) return [];
963
- if (isUnrendered(el)) return [hiddenSubtree(tree, el, parent, "notRendered")].filter(included);
964
- if (isInvisible(el)) return [hiddenSubtree(tree, el, parent, "notVisible")].filter(included);
965
- const registry = tree.options.registry;
966
- const roleInfo = roleInfoOf(el);
967
- let reasons;
968
- let childCtx = ctx;
969
- let nameSuppressed = false;
970
- let presentationalInherited = false;
971
- if ((ctx.blockedByModal || Boolean(tree.modal && !el.contains(tree.modal) && !tree.modal.contains(el))) && tree.modal) {
972
- reasons = [relatedReason("activeModalDialog", tree.modal, registry)];
973
- childCtx = {
974
- ...ctx,
975
- blockedByModal: true
976
- };
977
- } else if (el.getAttribute("aria-hidden") === "true") {
978
- reasons = [ignoredReason("ariaHiddenElement")];
979
- childCtx = {
980
- ...ctx,
981
- ariaHiddenBy: el
982
- };
983
- nameSuppressed = true;
984
- } else if (ctx.ariaHiddenBy) {
985
- reasons = [relatedReason("ariaHiddenSubtree", ctx.ariaHiddenBy, registry)];
986
- childCtx = ctx;
987
- nameSuppressed = true;
988
- } else if (el.hasAttribute("inert") || ctx.inert) {
989
- reasons = [ignoredReason("inertElement")];
990
- childCtx = {
991
- ...ctx,
992
- inert: true
993
- };
994
- } else if (isPresentational(el) || ctx.presentational && ctx.presentationalParent && inheritsPresentation(ctx.presentationalParent, el)) {
995
- reasons = [ignoredReason("presentationalRole")];
996
- presentationalInherited = !isPresentational(el);
997
- childCtx = {
998
- ...ctx,
999
- presentational: true,
1000
- presentationalParent: el
1001
- };
1002
- nameSuppressed = true;
1003
- roleInfo.wire = null;
1004
- roleInfo.mojom = "None";
1005
- } else if (el.localName === "canvas" && !roleHasExplicit(el)) {
1006
- reasons = [ignoredReason("probablyPresentational")];
1007
- childCtx = ctx;
1008
- } else if (roleInfo.wire === null && !isInterestingGeneric(el)) {
1009
- reasons = [ignoredReason("uninteresting")];
1010
- childCtx = ctx;
1011
- } else childCtx = {
1012
- ...ctx,
1013
- presentational: false,
1014
- presentationalParent: void 0
1015
- };
1016
- const dropText = Boolean(childCtx.ariaHiddenBy || childCtx.inert || childCtx.blockedByModal);
1017
- const leaf = el.localName === "svg" || el.localName === "iframe" || el.localName === "frame";
1018
- if (reasons) {
1019
- const obj = makeObj(tree, el, {
1020
- wireRole: roleInfo.wire,
1021
- mojom: roleInfo.mojom,
1022
- ignored: true,
1023
- included: presentationalInherited || renderedIgnoredIncluded(tree, el),
1024
- reasons,
1025
- nameSuppressed
1026
- });
1027
- const children = leaf ? [] : walkChildren(tree, el, obj, childCtx, dropText);
1028
- if (obj.included) {
1029
- obj.children = children;
1030
- for (const child of children) child.parent = obj;
1031
- obj.parent = parent;
1032
- return [obj];
1033
- }
1034
- obj.parent = parent;
1035
- for (const child of children) child.parent = parent;
1036
- return children;
1037
- }
1038
- const obj = makeObj(tree, el, {
1039
- wireRole: roleInfo.wire ?? "generic",
1040
- mojom: roleInfo.wire ? roleInfo.mojom : "GenericContainer",
1041
- ignored: false,
1042
- included: true
1043
- });
1044
- obj.parent = parent;
1045
- obj.markerText = listMarkerText(el);
1046
- if (el instanceof HTMLSelectElement && !el.multiple) {
1047
- const popup = {
1048
- id: `${obj.id}:popup`,
1049
- node: el,
1050
- wireRole: "MenuListPopup",
1051
- mojom: "MenuListPopup",
1052
- ignored: false,
1053
- included: true,
1054
- children: [],
1055
- isPopup: true,
1056
- parent: obj
1057
- };
1058
- tree.byId.set(popup.id, popup);
1059
- popup.children = walkChildren(tree, el, popup, childCtx, true);
1060
- for (const child of popup.children) {
1061
- child.parent = popup;
1062
- child.children = [];
1063
- }
1064
- obj.children = [popup];
1065
- return [obj];
1066
- }
1067
- obj.children = leaf ? [] : walkChildren(tree, el, obj, childCtx, dropText);
1068
- for (const child of obj.children) child.parent = obj;
1069
- return [obj];
1070
- }
1071
- function included(obj) {
1072
- return obj.included;
1073
- }
1074
- function roleHasExplicit(el) {
1075
- return explicitRole(el) !== null;
1076
- }
1077
- /** A role-less element is an exposed `generic` when it is block-level, named,
1078
- * or focusable; inline/contents wrappers are ignored as uninteresting. */
1079
- function isInterestingGeneric(el) {
1080
- if (el.localName === "html" || el.localName === "body") return false;
1081
- if (normalizeText(el.getAttribute("aria-label")) || el.hasAttribute("aria-labelledby") || el.hasAttribute("aria-labeledby")) return true;
1082
- if (isFocusable(el)) return true;
1083
- if (getComputedStyle(el).display === "contents") return false;
1084
- return !isInlineLevel(el);
1085
- }
1086
- function walkChildren(tree, el, parent, ctx, dropText) {
1087
- const out = [];
1088
- for (const child of composedChildren(el)) {
1089
- if (isText(child)) {
1090
- if (dropText) continue;
1091
- const text = staticTextValue(child);
1092
- if (!text) continue;
1093
- const obj = makeObj(tree, child, {
1094
- wireRole: "StaticText",
1095
- mojom: "StaticText",
1096
- ignored: false,
1097
- included: true,
1098
- text
1099
- });
1100
- obj.parent = parent;
1101
- out.push(obj);
1102
- continue;
1103
- }
1104
- if (isElement(child)) out.push(...walkElement(tree, child, parent, ctx));
1105
- }
1106
- return out;
1107
- }
1108
- /** Build the (excluded by default) object for an unrendered/invisible element
1109
- * and side-register its element descendants for direct inspection. */
1110
- function hiddenSubtree(tree, el, parent, reason) {
1111
- const roleInfo = roleInfoOf(el);
1112
- const obj = makeObj(tree, el, {
1113
- wireRole: roleInfo.wire,
1114
- mojom: roleInfo.mojom,
1115
- ignored: true,
1116
- included: hiddenButIncluded(tree, el),
1117
- reasons: [ignoredReason(reason)],
1118
- nameSuppressed: true
1119
- });
1120
- obj.parent = parent;
1121
- const registerDescendants = (cur) => {
1122
- for (const child of Array.from(cur.children)) {
1123
- if (isSkipped(child) || tree.byNode.has(child)) continue;
1124
- const info = roleInfoOf(child);
1125
- const childObj = makeObj(tree, child, {
1126
- wireRole: info.wire,
1127
- mojom: info.mojom,
1128
- ignored: true,
1129
- included: false,
1130
- reasons: [ignoredReason(reason)],
1131
- nameSuppressed: true
1132
- });
1133
- childObj.parent = parent;
1134
- registerDescendants(child);
1135
- }
1136
- };
1137
- registerDescendants(el);
1138
- return obj;
1139
- }
1140
- /** Space-joined text equivalent of an element's contents. Skips hidden
1141
- * descendants unless `unfiltered`; descendant aria-labels and img alts
1142
- * substitute for their subtrees (Blink joins chunks with single spaces). */
1143
- function textEquivalent(root, unfiltered = false) {
1144
- const chunks = [];
1145
- const visit = (node) => {
1146
- if (isText(node)) {
1147
- const text = normalizeText(node.nodeValue);
1148
- if (text) chunks.push(text);
1149
- return;
1150
- }
1151
- if (!isElement(node)) return;
1152
- if (isSkipped(node) && node.localName !== "title") return;
1153
- if (!unfiltered && isElHiddenForText(node)) return;
1154
- const ariaLabel = normalizeText(node.getAttribute("aria-label"));
1155
- if (ariaLabel) {
1156
- chunks.push(ariaLabel);
1157
- return;
1158
- }
1159
- if (node.localName === "img" || node instanceof HTMLInputElement && node.type === "image") {
1160
- const alt = normalizeText(node.getAttribute("alt"));
1161
- if (isPresentational(node)) {
1162
- if (node.hasAttribute("alt")) chunks.push("");
1163
- } else if (alt) chunks.push(alt);
1164
- return;
1165
- }
1166
- if (node instanceof HTMLInputElement) {
1167
- const value = normalizeText(node.value);
1168
- if (value) chunks.push(value);
1169
- return;
1170
- }
1171
- for (const child of composedChildren(node)) visit(child);
1172
- for (const id of idRefs(node, "aria-owns")) {
1173
- const owned = node.ownerDocument.getElementById(id);
1174
- if (owned && !visited.has(owned)) {
1175
- visited.add(owned);
1176
- visit(owned);
1177
- }
1178
- }
1179
- };
1180
- const visited = /* @__PURE__ */ new Set();
1181
- for (const child of composedChildren(root)) visit(child);
1182
- for (const id of idRefs(root, "aria-owns")) {
1183
- const owned = root.ownerDocument.getElementById(id);
1184
- if (owned && !visited.has(owned)) {
1185
- visited.add(owned);
1186
- visit(owned);
1187
- }
1188
- }
1189
- return chunks.join(" ");
1190
- }
1191
- /** Text contributed by one aria-labelledby target: its aria-label wins;
1192
- * hidden targets contribute their full unfiltered subtree text; labelledby
1193
- * chains are NOT followed (the reference is non-recursive). */
1194
- function labelledbyTargetText(target) {
1195
- const ariaLabel = normalizeText(target.getAttribute("aria-label"));
1196
- if (ariaLabel) return ariaLabel;
1197
- return textEquivalent(target, hasHiddenAncestorOrSelf(target));
1198
- }
1199
- function labelText(label) {
1200
- if (hasHiddenAncestorOrSelf(label)) return "";
1201
- return textEquivalent(label);
1202
- }
1203
- const NAME_FROM_CONTENTS_ROLES = new Set([
1204
- "button",
1205
- "DisclosureTriangle",
1206
- "cell",
1207
- "checkbox",
1208
- "columnheader",
1209
- "gridcell",
1210
- "heading",
1211
- "link",
1212
- "menuitem",
1213
- "menuitemcheckbox",
1214
- "menuitemradio",
1215
- "option",
1216
- "radio",
1217
- "row",
1218
- "rowheader",
1219
- "switch",
1220
- "tab",
1221
- "tooltip",
1222
- "treeitem"
1223
- ]);
1224
- function idRefs(el, ...names) {
1225
- for (const name of names) {
1226
- const raw = el.getAttribute(name);
1227
- if (raw != null) return raw.trim().split(/\s+/).filter(Boolean);
1228
- }
1229
- return [];
1230
- }
1231
- function isLabelable(el) {
1232
- return el instanceof HTMLButtonElement || el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || el instanceof HTMLOutputElement || el instanceof HTMLMeterElement || el instanceof HTMLProgressElement;
1233
- }
1234
- function nativeLabels(el) {
1235
- const labels = el.labels;
1236
- return labels ? Array.from(labels) : [];
1237
- }
1238
- function labelledbyCandidate(tree, el) {
1239
- const registry = tree.options.registry;
1240
- const ids = idRefs(el, "aria-labelledby", "aria-labeledby");
1241
- const source = {
1242
- type: "relatedElement",
1243
- attribute: "aria-labelledby"
1244
- };
1245
- if (!ids.length) return {
1246
- source,
1247
- value: null
1248
- };
1249
- const targets = ids.map((id) => ({
1250
- id,
1251
- el: el.ownerDocument.getElementById(id)
1252
- })).filter((t) => t.el != null);
1253
- const texts = targets.map((t) => labelledbyTargetText(t.el));
1254
- if (!texts.filter(Boolean).join(" ")) {
1255
- source.attributeValue = {
1256
- type: "string",
1257
- value: el.getAttribute("aria-labelledby") ?? ""
1258
- };
1259
- source.invalid = true;
1260
- return {
1261
- source,
1262
- value: null
1263
- };
1264
- }
1265
- const related = targets.map((t, i) => {
1266
- const node = {
1267
- backendDOMNodeId: registry.backendIdFor(t.el),
1268
- idref: t.id
1269
- };
1270
- if (texts[i]) node.text = texts[i];
1271
- return node;
1272
- });
1273
- source.attributeValue = {
1274
- type: "idrefList",
1275
- value: ids.join(" "),
1276
- ...related.length ? { relatedNodes: related } : {}
1277
- };
1278
- const propertyRelated = targets.map((t, i) => ({
1279
- backendDOMNodeId: registry.backendIdFor(t.el),
1280
- idref: t.id,
1281
- text: texts[i] ?? ""
1282
- }));
1283
- return {
1284
- source,
1285
- value: texts.filter(Boolean).join(" "),
1286
- related: propertyRelated
1287
- };
1288
- }
1289
- function attributeCandidate(el, attribute, options = {}) {
1290
- const raw = el.getAttribute(attribute);
1291
- const source = {
1292
- type: options.sourceType ?? "attribute",
1293
- attribute
1294
- };
1295
- if (raw != null && raw !== "" && !options.omitAttributeValue) source.attributeValue = {
1296
- type: "string",
1297
- value: raw
1298
- };
1299
- const text = normalizeText(raw);
1300
- if (text) return {
1301
- source,
1302
- value: text
1303
- };
1304
- if (options.defaultValue !== void 0 && raw !== null) return {
1305
- source,
1306
- value: options.defaultValue
1307
- };
1308
- return {
1309
- source,
1310
- value: raw == null ? null : ""
1311
- };
1312
- }
1313
- function nativeLabelCandidate(tree, el) {
1314
- const registry = tree.options.registry;
1315
- const labels = nativeLabels(el);
1316
- const wrapped = labels.some((label) => label.contains(el));
1317
- const source = {
1318
- type: "relatedElement",
1319
- nativeSource: labels.length ? wrapped ? "labelwrapped" : "labelfor" : "label"
1320
- };
1321
- if (!labels.length) return {
1322
- source,
1323
- value: null
1324
- };
1325
- const texts = labels.map((label) => labelText(label));
1326
- source.nativeSourceValue = {
1327
- type: "nodeList",
1328
- relatedNodes: labels.map((label, i) => ({
1329
- backendDOMNodeId: registry.backendIdFor(label),
1330
- text: texts[i] ?? ""
1331
- }))
1332
- };
1333
- const propertyRelated = labels.map((label, i) => ({
1334
- backendDOMNodeId: registry.backendIdFor(label),
1335
- text: texts[i] ?? ""
1336
- }));
1337
- return {
1338
- source,
1339
- value: texts.filter(Boolean).join(" "),
1340
- related: propertyRelated,
1341
- terminal: true
1342
- };
1343
- }
1344
- function relatedElementCandidate(tree, nativeSource, els) {
1345
- const registry = tree.options.registry;
1346
- const source = {
1347
- type: "relatedElement",
1348
- nativeSource
1349
- };
1350
- if (!els.length) return {
1351
- source,
1352
- value: null
1353
- };
1354
- const texts = els.map((e) => textEquivalent(e));
1355
- source.nativeSourceValue = {
1356
- type: "nodeList",
1357
- relatedNodes: els.map((e, i) => {
1358
- const node = { backendDOMNodeId: registry.backendIdFor(e) };
1359
- if (texts[i]) node.text = texts[i];
1360
- return node;
1361
- })
1362
- };
1363
- const propertyRelated = els.map((e, i) => ({
1364
- backendDOMNodeId: registry.backendIdFor(e),
1365
- text: texts[i] ?? ""
1366
- }));
1367
- return {
1368
- source,
1369
- value: texts.filter(Boolean).join(" "),
1370
- related: propertyRelated
1371
- };
1372
- }
1373
- function contentsCandidate(el, defaultValue = "") {
1374
- const id = el.getAttribute("id");
1375
- if (id && idRefs(el, "aria-labelledby", "aria-labeledby").includes(id)) return {
1376
- source: { type: "contents" },
1377
- value: null
1378
- };
1379
- return {
1380
- source: { type: "contents" },
1381
- value: textEquivalent(el) || defaultValue || null
1382
- };
1383
- }
1384
- function svgTitleCandidate(el) {
1385
- const title = Array.from(el.children).find((child) => child.localName === "title");
1386
- const source = {
1387
- type: "relatedElement",
1388
- nativeSource: "title"
1389
- };
1390
- if (!title) return {
1391
- source,
1392
- value: null
1393
- };
1394
- return {
1395
- source,
1396
- value: normalizeText(title.textContent)
1397
- };
1398
- }
1399
- /** Ordered AccName candidates, element-specific (Chromium's NameSources). */
1400
- function nameCandidates(tree, el, role) {
1401
- const base = [labelledbyCandidate(tree, el), attributeCandidate(el, "aria-label")];
1402
- if (NAME_PROHIBITED_ROLES.has(role)) return base;
1403
- const tag = el.localName;
1404
- const title = () => attributeCandidate(el, "title");
1405
- if (el instanceof HTMLInputElement) {
1406
- const type = el.type;
1407
- if ([
1408
- "button",
1409
- "submit",
1410
- "reset"
1411
- ].includes(type)) {
1412
- const label = normalizeText(el.getAttribute("value")) || ({
1413
- submit: "Submit",
1414
- reset: "Reset"
1415
- }[type] ?? "");
1416
- return [
1417
- ...base,
1418
- nativeLabelCandidate(tree, el),
1419
- attributeCandidate(el, "value", { omitAttributeValue: true }),
1420
- {
1421
- source: { type: "contents" },
1422
- value: label || null
1423
- },
1424
- title()
1425
- ];
1426
- }
1427
- if (type === "image") {
1428
- const typeSource = {
1429
- type: "attribute",
1430
- attribute: "type"
1431
- };
1432
- const rawType = el.getAttribute("type");
1433
- if (rawType) typeSource.attributeValue = {
1434
- type: "string",
1435
- value: rawType
1436
- };
1437
- const label = normalizeText(el.getAttribute("alt")) || normalizeText(el.getAttribute("value")) || normalizeText(el.getAttribute("title")) || "Submit";
1438
- return [
1439
- ...base,
1440
- nativeLabelCandidate(tree, el),
1441
- attributeCandidate(el, "alt"),
1442
- attributeCandidate(el, "value", { omitAttributeValue: true }),
1443
- title(),
1444
- {
1445
- source: typeSource,
1446
- value: "Submit"
1447
- },
1448
- {
1449
- source: { type: "contents" },
1450
- value: label
1451
- },
1452
- title()
1453
- ];
1454
- }
1455
- if (TEXT_ENTRY_INPUTS.has(type) || type === "number") return [
1456
- ...base,
1457
- nativeLabelCandidate(tree, el),
1458
- title(),
1459
- attributeCandidate(el, "placeholder", { sourceType: "placeholder" }),
1460
- attributeCandidate(el, "aria-placeholder", { sourceType: "placeholder" }),
1461
- title()
1462
- ];
1463
- const slots = [...base, nativeLabelCandidate(tree, el)];
1464
- if (NAME_FROM_CONTENTS_ROLES.has(role)) slots.push(contentsCandidate(el));
1465
- return [...slots, title()];
1466
- }
1467
- if (tag === "textarea") return [
1468
- ...base,
1469
- nativeLabelCandidate(tree, el),
1470
- title(),
1471
- attributeCandidate(el, "placeholder", { sourceType: "placeholder" }),
1472
- attributeCandidate(el, "aria-placeholder", { sourceType: "placeholder" }),
1473
- title()
1474
- ];
1475
- if (tag === "img") {
1476
- if (el.getAttribute("alt") === "") {
1477
- const source = {
1478
- type: "attribute",
1479
- attribute: "alt",
1480
- attributeValue: {
1481
- type: "string",
1482
- value: ""
1483
- }
1484
- };
1485
- return [...base, {
1486
- source,
1487
- value: "",
1488
- terminal: true
1489
- }];
1490
- }
1491
- return [
1492
- ...base,
1493
- attributeCandidate(el, "alt"),
1494
- title()
1495
- ];
1496
- }
1497
- if (tag === "svg") return [
1498
- ...base,
1499
- svgTitleCandidate(el),
1500
- title()
1501
- ];
1502
- if (tag === "figure") return [...base, title()];
1503
- if (tag === "fieldset") return [
1504
- ...base,
1505
- relatedElementCandidate(tree, "legend", queryChildren(el, "legend")),
1506
- title()
1507
- ];
1508
- if (tag === "table") return [
1509
- ...base,
1510
- relatedElementCandidate(tree, "tablecaption", queryChildren(el, "caption")),
1511
- title()
1512
- ];
1513
- const slots = [...base];
1514
- if (isLabelable(el)) slots.push(nativeLabelCandidate(tree, el));
1515
- if (NAME_FROM_CONTENTS_ROLES.has(role)) slots.push(contentsCandidate(el));
1516
- slots.push(title());
1517
- return slots;
1518
- }
1519
- function queryChildren(el, selector) {
1520
- const match = el.querySelector(selector);
1521
- return match ? [match] : [];
1522
- }
1523
- function computeName(tree, el, role) {
1524
- const candidates = nameCandidates(tree, el, role);
1525
- const winner = candidates.findIndex((candidate) => candidate.value || candidate.terminal && candidate.value !== null);
1526
- const sources = candidates.map((candidate, index) => {
1527
- const source = { ...candidate.source };
1528
- if (index === winner) source.value = ax("computedString", candidate.value);
1529
- else if (winner >= 0 && index > winner) {
1530
- source.superseded = true;
1531
- if (candidate.value) source.value = ax("computedString", candidate.value);
1532
- }
1533
- return source;
1534
- });
1535
- const effective = winner >= 0 ? candidates[winner] : candidates.find((candidate) => candidate.value !== null && candidate.related?.length);
1536
- return {
1537
- value: winner >= 0 ? normalizeText(candidates[winner]?.value ?? "") : "",
1538
- sources,
1539
- labelledbyRelated: effective?.related,
1540
- fromAuthorAria: winner === 0 || winner === 1
1541
- };
1542
- }
1543
- /** RootWebArea name sources: Chromium emits this fixed quirky list. */
1544
- function rootNameSources(title) {
1545
- const sources = [
1546
- {
1547
- type: "relatedElement",
1548
- attribute: "aria-labelledby"
1549
- },
1550
- {
1551
- type: "attribute",
1552
- attribute: "aria-label"
1553
- },
1554
- {
1555
- type: "attribute",
1556
- attribute: "aria-label",
1557
- superseded: true
1558
- }
1559
- ];
1560
- const native = {
1561
- type: "relatedElement",
1562
- nativeSource: "title"
1563
- };
1564
- if (title) native.value = ax("computedString", title);
1565
- sources.push(native);
1566
- return sources;
1567
- }
1568
- function boolAttr(el, name) {
1569
- const value = el.getAttribute(name);
1570
- if (value == null) return void 0;
1571
- if (value === "" || value === "true") return true;
1572
- if (value === "false") return false;
1573
- }
1574
- function tristateAttr(el, name) {
1575
- const value = el.getAttribute(name);
1576
- if (value === "mixed") return "mixed";
1577
- if (value === "true") return true;
1578
- if (value === "false") return false;
1579
- }
1580
- function tristateValue(value) {
1581
- if (value === void 0) return void 0;
1582
- if (value === "mixed") return "mixed";
1583
- return value ? "true" : "false";
1584
- }
1585
- function addProp(props, name, type, value, options = {}) {
1586
- if (value !== void 0 && value !== null && value !== "" && (options.includeFalse || value !== false)) props.push({
1587
- name,
1588
- value: ax(type, value)
1589
- });
1590
- }
1591
- function isDisabled(el) {
1592
- if (nativeDisabled(el) || boolAttr(el, "aria-disabled") === true) return true;
1593
- for (let parent = el.parentElement; parent; parent = parent.parentElement) if (parent.getAttribute("aria-disabled") === "true") return true;
1594
- return false;
1595
- }
1596
- const READONLY_ROLES = new Set([
1597
- "grid",
1598
- "gridcell",
1599
- "textbox",
1600
- "searchbox",
1601
- "columnheader",
1602
- "rowheader",
1603
- "treegrid"
1604
- ]);
1605
- const REQUIRED_ROLES = new Set([
1606
- "combobox",
1607
- "gridcell",
1608
- "listbox",
1609
- "radiogroup",
1610
- "spinbutton",
1611
- "textbox",
1612
- "searchbox",
1613
- "tree",
1614
- "columnheader",
1615
- "rowheader",
1616
- "treegrid"
1617
- ]);
1618
- const MULTISELECTABLE_ROLES = new Set([
1619
- "grid",
1620
- "listbox",
1621
- "tablist",
1622
- "treegrid",
1623
- "tree"
1624
- ]);
1625
- function readonlyState(el) {
1626
- if ("readOnly" in el && Boolean(el.readOnly)) return true;
1627
- return boolAttr(el, "aria-readonly") === true;
1628
- }
1629
- function requiredState(el) {
1630
- if ("required" in el && Boolean(el.required)) return true;
1631
- return boolAttr(el, "aria-required") === true;
1632
- }
1633
- function multiselectableState(el) {
1634
- if (el instanceof HTMLSelectElement && el.multiple) return true;
1635
- return boolAttr(el, "aria-multiselectable") === true;
1636
- }
1637
- function headingLevel(el) {
1638
- const aria = Number(el.getAttribute("aria-level"));
1639
- if (Number.isFinite(aria) && aria > 0) return aria;
1640
- const match = /^h([1-6])$/.exec(el.localName);
1641
- return match?.[1] ? Number(match[1]) : void 0;
1642
- }
1643
- function nativeMin(el, role) {
1644
- if (el instanceof HTMLInputElement) {
1645
- if (el.type === "range") return numberAttr(el, "min") ?? 0;
1646
- if (el.type === "number") return numberAttr(el, "min");
1647
- }
1648
- if (el instanceof HTMLProgressElement) return 0;
1649
- if (el instanceof HTMLMeterElement) return el.min;
1650
- if (role === "scrollbar") return 0;
1651
- }
1652
- function nativeMax(el, role) {
1653
- if (el instanceof HTMLInputElement) {
1654
- if (el.type === "range") return numberAttr(el, "max") ?? 100;
1655
- if (el.type === "number") return numberAttr(el, "max");
1656
- }
1657
- if (el instanceof HTMLProgressElement) return el.max;
1658
- if (el instanceof HTMLMeterElement) return el.max;
1659
- if (role === "scrollbar") return 100;
1660
- }
1661
- const RANGE_ROLES = new Set([
1662
- "slider",
1663
- "scrollbar",
1664
- "spinbutton",
1665
- "progressbar",
1666
- "meter",
1667
- "separator"
1668
- ]);
1669
- /** The numeric value for a range-valued element, or undefined to omit it. */
1670
- function rangeValue(el, role) {
1671
- const min = numberAttr(el, "aria-valuemin") ?? nativeMin(el, role);
1672
- const max = numberAttr(el, "aria-valuemax") ?? nativeMax(el, role);
1673
- const clamp = (n) => Math.min(max ?? n, Math.max(min ?? n, n));
1674
- const ariaNow = numberAttr(el, "aria-valuenow");
1675
- if (ariaNow != null) return clamp(ariaNow);
1676
- if (el instanceof HTMLInputElement && (el.type === "range" || el.type === "number")) return Number.isFinite(el.valueAsNumber) ? el.valueAsNumber : void 0;
1677
- if (el instanceof HTMLProgressElement) return el.hasAttribute("value") ? el.value : void 0;
1678
- if (el instanceof HTMLMeterElement) return el.value;
1679
- const lo = min ?? 0;
1680
- const hi = max ?? 100;
1681
- if (role === "slider" || role === "scrollbar" || role === "separator") return (lo + hi) / 2;
1682
- if (role === "spinbutton") return lo;
1683
- }
1684
- function selectValue(el) {
1685
- if (el.multiple) return void 0;
1686
- const option = el.selectedOptions[0];
1687
- if (!option) return void 0;
1688
- const text = normalizeText(option.getAttribute("aria-label") || option.textContent);
1689
- return text ? ax("string", text) : void 0;
1690
- }
1691
- function isTextEntryControl(el) {
1692
- if (el instanceof HTMLTextAreaElement) return true;
1693
- if (el instanceof HTMLInputElement) return TEXT_ENTRY_INPUTS.has(el.type);
1694
- return false;
1695
- }
1696
- function valueFor(el, role) {
1697
- const override = el.__agentAX?.value;
1698
- if (override !== void 0) return ax(typeof override === "number" ? "number" : "string", override);
1699
- if (RANGE_ROLES.has(role) || el instanceof HTMLProgressElement || el instanceof HTMLMeterElement) {
1700
- const number = rangeValue(el, role);
1701
- return number == null ? void 0 : ax("number", number);
1702
- }
1703
- if (el instanceof HTMLSelectElement) return selectValue(el);
1704
- if (isTextEntryControl(el)) {
1705
- if (el instanceof HTMLInputElement && el.type === "password") return el.value ? ax("string", "•".repeat(el.value.length)) : void 0;
1706
- const value = el.value;
1707
- return value ? ax("string", value) : void 0;
1708
- }
1709
- if (contentEditable(el)) {
1710
- const text = normalizeText(el.textContent);
1711
- return text ? ax("string", text) : void 0;
1712
- }
1713
- }
1714
- function ariaToken(el, name) {
1715
- const value = el.getAttribute(name);
1716
- if (value == null || value === "" || value === "undefined") return void 0;
1717
- if (value === "true") return true;
1718
- if (value === "false") return false;
1719
- return value;
1720
- }
1721
- function relatedNodesFor(registry, el, ids) {
1722
- return ids.flatMap((idref) => {
1723
- const related = el.ownerDocument.getElementById(idref);
1724
- if (!related) return [];
1725
- return [{
1726
- backendDOMNodeId: registry.backendIdFor(related),
1727
- idref
1728
- }];
1729
- });
1730
- }
1731
- function addRelationProp(registry, props, el, attr, name, type, settings = {}) {
1732
- const ids = idRefs(el, attr);
1733
- if (!ids.length) return;
1734
- const value = { type };
1735
- if (!settings.omitValue && type === "idref") value.value = ids[0];
1736
- if (!settings.omitValue && type === "idrefList") value.value = ids.join(" ");
1737
- const relatedNodes = relatedNodesFor(registry, el, ids);
1738
- if (relatedNodes.length) value.relatedNodes = relatedNodes;
1739
- props.push({
1740
- name,
1741
- value
1742
- });
1743
- }
1744
- function roleSupportsAria(role, attr) {
1745
- const definition = roles.get(role === "image" ? "img" : role);
1746
- return !definition || attr in definition.props || attr in definition.requiredProps;
1747
- }
1748
- function addAriaProp(props, el, role, attr, name, type, value, options = {}) {
1749
- if (!el.hasAttribute(attr) || !roleSupportsAria(role, attr)) return;
1750
- addProp(props, name, type, value, options);
1751
- }
1752
- const CHECK_DEFAULT_FALSE_ROLES = new Set([
1753
- "checkbox",
1754
- "radio",
1755
- "switch",
1756
- "menuitemcheckbox",
1757
- "menuitemradio"
1758
- ]);
1759
- const SELECTABLE_ROLES = new Set([
1760
- "option",
1761
- "tab",
1762
- "row",
1763
- "gridcell",
1764
- "treeitem",
1765
- "columnheader",
1766
- "rowheader"
1767
- ]);
1768
- function liveStatus(el, role) {
1769
- const aria = el.getAttribute("aria-live");
1770
- if (aria && aria !== "off") return aria;
1771
- if (role === "alert") return "assertive";
1772
- if (role === "status" || role === "log") return "polite";
1773
- }
1774
- /** The invalid token; native form controls always carry one (default false). */
1775
- function invalidToken(el) {
1776
- const aria = el.getAttribute("aria-invalid");
1777
- if (aria === "grammar" || aria === "spelling") return aria;
1778
- if (aria === "true" || aria === "") return "true";
1779
- if (aria === "false") return "false";
1780
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || el instanceof HTMLButtonElement) return "false";
1781
- }
1782
- /** The editable token ("plaintext"/"richtext") for editable elements. */
1783
- function editableToken(el) {
1784
- if (contentEditable(el)) return el.getAttribute("contenteditable") === "plaintext-only" ? "plaintext" : "richtext";
1785
- if (el instanceof HTMLTextAreaElement) return "plaintext";
1786
- if (el instanceof HTMLInputElement) return TEXT_ENTRY_INPUTS.has(el.type) ? "plaintext" : void 0;
1787
- }
1788
- /** Only text-entry-like controls are settable (not checkboxes or buttons). */
1789
- function isSettable(el) {
1790
- if (isTextEntryControl(el) || contentEditable(el)) return true;
1791
- if (el instanceof HTMLInputElement) return [
1792
- "number",
1793
- "range",
1794
- "color",
1795
- "date",
1796
- "datetime-local",
1797
- "month",
1798
- "week",
1799
- "time"
1800
- ].includes(el.type);
1801
- return false;
1802
- }
1803
- function checkedState(el, role) {
1804
- if (el instanceof HTMLInputElement && (el.type === "checkbox" || el.type === "radio")) return el.indeterminate ? "mixed" : el.checked;
1805
- const aria = tristateAttr(el, "aria-checked");
1806
- if (aria !== void 0) return aria;
1807
- return CHECK_DEFAULT_FALSE_ROLES.has(role) ? false : void 0;
1808
- }
1809
- function expandedState(el) {
1810
- const aria = boolAttr(el, "aria-expanded");
1811
- if (aria !== void 0) return aria;
1812
- const details = el.localName === "details" ? el : el.localName === "summary" ? el.parentElement : null;
1813
- if (details instanceof HTMLDetailsElement) return details.open;
1814
- if (el instanceof HTMLSelectElement && !el.multiple) return false;
1815
- }
1816
- function isModalDialog(tree, el) {
1817
- return tree.modal === el;
1818
- }
1819
- /** Where the dialog focusing steps put focus after showModal(): the first
1820
- * focusable descendant, else the dialog itself. jsdom never runs these
1821
- * steps, so emulate them when focus is still on the body. */
1822
- function modalFocusTarget(tree) {
1823
- if (!tree.modal) return null;
1824
- const active = tree.options.document.activeElement;
1825
- if (active && active !== tree.options.document.body && active.localName !== "html") return null;
1826
- for (const el of Array.from(tree.modal.querySelectorAll("*"))) if (!isUnrendered(el) && isFocusable(el)) return el;
1827
- return tree.modal;
1828
- }
1829
- function propertiesFor(tree, el, role, name) {
1830
- const registry = tree.options.registry;
1831
- const props = [];
1832
- const live = liveStatus(el, role);
1833
- if (live) {
1834
- addProp(props, "live", "token", live);
1835
- addProp(props, "atomic", "boolean", boolAttr(el, "aria-atomic") === true, { includeFalse: true });
1836
- addProp(props, "relevant", "tokenList", el.getAttribute("aria-relevant") || "additions text");
1837
- }
1838
- addProp(props, "disabled", "boolean", isDisabled(el));
1839
- addProp(props, "invalid", "token", invalidToken(el));
1840
- addProp(props, "focusable", "booleanOrUndefined", isFocusable(el));
1841
- addProp(props, "focused", "booleanOrUndefined", el === el.ownerDocument.activeElement || el === modalFocusTarget(tree));
1842
- addProp(props, "editable", "token", editableToken(el));
1843
- if (isSettable(el)) addProp(props, "settable", "booleanOrUndefined", true);
1844
- addProp(props, "autocomplete", "token", ariaToken(el, "aria-autocomplete"));
1845
- const haspopup = el.getAttribute("aria-haspopup");
1846
- if (haspopup && haspopup !== "false") addProp(props, "hasPopup", "token", haspopup);
1847
- else if (el instanceof HTMLSelectElement && !el.multiple) addProp(props, "hasPopup", "token", "menu");
1848
- addProp(props, "level", "integer", headingLevel(el));
1849
- if (MULTISELECTABLE_ROLES.has(role)) addProp(props, "multiselectable", "boolean", multiselectableState(el), { includeFalse: true });
1850
- addProp(props, "orientation", "token", ariaToken(el, "aria-orientation"));
1851
- if (role === "textbox" || role === "searchbox") addProp(props, "multiline", "boolean", el.localName === "textarea" || boolAttr(el, "aria-multiline") === true, { includeFalse: true });
1852
- if (READONLY_ROLES.has(role)) addProp(props, "readonly", "boolean", readonlyState(el), { includeFalse: true });
1853
- if (REQUIRED_ROLES.has(role) && !(el instanceof HTMLSelectElement)) addProp(props, "required", "boolean", requiredState(el), { includeFalse: true });
1854
- if (RANGE_ROLES.has(role)) {
1855
- addProp(props, "valuemin", "number", numberAttr(el, "aria-valuemin") ?? nativeMin(el, role));
1856
- addProp(props, "valuemax", "number", numberAttr(el, "aria-valuemax") ?? nativeMax(el, role));
1857
- addProp(props, "valuetext", "string", el.getAttribute("aria-valuetext"));
1858
- }
1859
- if (role === "link" && el instanceof HTMLAnchorElement) addProp(props, "url", "string", el.href);
1860
- if (role === "image" && el instanceof HTMLImageElement) addProp(props, "url", "string", el.src);
1861
- if (el instanceof HTMLInputElement && el.type === "image") addProp(props, "url", "string", el.src);
1862
- const pressed = tristateAttr(el, "aria-pressed");
1863
- if (pressed !== void 0) addProp(props, "pressed", "tristate", tristateValue(pressed), { includeFalse: true });
1864
- else {
1865
- const checked = checkedState(el, role);
1866
- if (checked !== void 0) addProp(props, "checked", "tristate", tristateValue(checked), { includeFalse: true });
1867
- }
1868
- const expanded = expandedState(el);
1869
- if (expanded !== void 0) addProp(props, "expanded", "booleanOrUndefined", expanded, { includeFalse: true });
1870
- if (SELECTABLE_ROLES.has(role)) {
1871
- const selected = el instanceof HTMLOptionElement ? el.selected : boolAttr(el, "aria-selected");
1872
- if (selected !== void 0) addProp(props, "selected", "booleanOrUndefined", selected, { includeFalse: true });
1873
- }
1874
- if (el.localName === "dialog") addProp(props, "modal", "boolean", isModalDialog(tree, el), { includeFalse: true });
1875
- else addAriaProp(props, el, role, "aria-modal", "modal", "boolean", boolAttr(el, "aria-modal"), { includeFalse: true });
1876
- addRelationProp(registry, props, el, "aria-describedby", "describedby", "idrefList");
1877
- addRelationProp(registry, props, el, "aria-owns", "owns", "idrefList");
1878
- addAriaProp(props, el, role, "aria-busy", "busy", "boolean", boolAttr(el, "aria-busy"));
1879
- addAriaProp(props, el, role, "aria-keyshortcuts", "keyshortcuts", "string", el.getAttribute("aria-keyshortcuts"));
1880
- addAriaProp(props, el, role, "aria-roledescription", "roledescription", "string", el.getAttribute("aria-roledescription"));
1881
- addRelationProp(registry, props, el, "aria-activedescendant", "activedescendant", "idref", { omitValue: true });
1882
- addRelationProp(registry, props, el, "aria-errormessage", "errormessage", "idrefList");
1883
- addRelationProp(registry, props, el, "aria-controls", "controls", "idrefList");
1884
- addRelationProp(registry, props, el, "aria-details", "details", "idrefList");
1885
- addRelationProp(registry, props, el, "aria-flowto", "flowto", "idrefList");
1886
- if (name.labelledbyRelated?.length) props.push({
1887
- name: "labelledby",
1888
- value: {
1889
- type: "nodeList",
1890
- relatedNodes: name.labelledbyRelated
1891
- }
1892
- });
1893
- return props;
1894
- }
1895
- function childWireIds(tree, obj) {
1896
- const ids = [];
1897
- if (obj.markerText) ids.push(`${obj.id}:marker`);
1898
- for (const child of obj.children) ids.push(child.id);
1899
- return ids;
1900
- }
1901
- function markerNodes(tree, obj) {
1902
- if (!obj.markerText) return [];
1903
- const registry = tree.options.registry;
1904
- const markerId = `${obj.id}:marker`;
1905
- const textId = `${markerId}:text`;
1906
- return [{
1907
- nodeId: markerId,
1908
- ignored: false,
1909
- role: roleNameValue("ListMarker"),
1910
- chromeRole: chromeRoleValue("ListMarker"),
1911
- name: ax("computedString", obj.markerText),
1912
- parentId: obj.id,
1913
- backendDOMNodeId: registry.backendIdFor(obj.node),
1914
- childIds: [textId]
1915
- }, {
1916
- nodeId: textId,
1917
- ignored: false,
1918
- role: roleNameValue("StaticText"),
1919
- chromeRole: chromeRoleValue("StaticText"),
1920
- name: ax("computedString", obj.markerText),
1921
- parentId: markerId,
1922
- backendDOMNodeId: registry.backendIdFor(obj.node),
1923
- childIds: []
1924
- }];
1925
- }
1926
- function forcedRoleOf(obj) {
1927
- if (obj.wireRole == null) {
1928
- if (obj.reasons?.some((reason) => reason.name === "uninteresting" || reason.name === "notRendered" || reason.name === "notVisible" || reason.name === "ariaHiddenElement" || reason.name === "ariaHiddenSubtree" || reason.name === "inertElement" || reason.name === "activeModalDialog") && !obj.reasons?.some((reason) => reason.name === "presentationalRole")) return {
1929
- role: ax("role", "generic"),
1930
- chrome: chromeRoleValue("GenericContainer")
1931
- };
1932
- return {
1933
- role: ax("role", "none"),
1934
- chrome: chromeRoleValue("None")
1935
- };
1936
- }
1937
- return {
1938
- role: roleNameValue(obj.wireRole),
1939
- chrome: chromeRoleValue(obj.mojom)
1940
- };
1941
- }
1942
- function nameOf(tree, obj) {
1943
- if (obj.isRoot) {
1944
- const title = tree.options.document.title || "";
1945
- return {
1946
- value: title,
1947
- sources: rootNameSources(title)
1948
- };
1949
- }
1950
- if (obj.text !== void 0) return {
1951
- value: obj.text,
1952
- sources: [{
1953
- type: "contents",
1954
- value: ax("computedString", obj.text)
1955
- }]
1956
- };
1957
- if (obj.isPopup) return {
1958
- value: "",
1959
- sources: []
1960
- };
1961
- const el = obj.node;
1962
- return computeName(tree, el, obj.wireRole ?? "generic");
1963
- }
1964
- /** The name used for queryAXTree matching: suppressed (empty) for hidden /
1965
- * presentational nodes, mirroring Blink's ComputedName(). */
1966
- function matchName(tree, obj) {
1967
- if (obj.ignored && obj.nameSuppressed) return "";
1968
- return nameOf(tree, obj).value;
1969
- }
1970
- function wireNode(tree, obj, force = false) {
1971
- const registry = tree.options.registry;
1972
- const node = {
1973
- nodeId: obj.id,
1974
- ignored: obj.ignored
1975
- };
1976
- if (obj.ignored && !force) {
1977
- node.role = ax("role", "none");
1978
- node.chromeRole = chromeRoleValue("None");
1979
- node.ignoredReasons = obj.reasons ?? [];
1980
- } else if (obj.ignored && force) {
1981
- const forced = forcedRoleOf(obj);
1982
- node.role = forced.role;
1983
- node.chromeRole = forced.chrome;
1984
- node.name = ax("computedString", matchName(tree, obj));
1985
- node.ignoredReasons = obj.reasons ?? [];
1986
- } else {
1987
- node.role = roleNameValue(obj.wireRole ?? "generic");
1988
- node.chromeRole = chromeRoleValue(obj.mojom);
1989
- const name = nameOf(tree, obj);
1990
- const nameValue = ax("computedString", name.value);
1991
- if (name.sources.length) nameValue.sources = name.sources;
1992
- node.name = nameValue;
1993
- if (obj.isRoot) node.properties = [{
1994
- name: "focusable",
1995
- value: ax("booleanOrUndefined", true)
1996
- }, {
1997
- name: "url",
1998
- value: ax("string", tree.options.document.URL)
1999
- }];
2000
- else if (obj.text !== void 0 || obj.isPopup) node.properties = [];
2001
- else {
2002
- const el = obj.node;
2003
- const role = obj.wireRole ?? "generic";
2004
- const description = descriptionFor(el, role, name);
2005
- if (description) node.description = ax("computedString", description);
2006
- const value = valueFor(el, role);
2007
- if (value) node.value = value;
2008
- node.properties = propertiesFor(tree, el, role, name);
2009
- }
2010
- }
2011
- node.childIds = childWireIds(tree, obj);
2012
- if (!obj.isPopup) node.backendDOMNodeId = registry.backendIdFor(obj.node);
2013
- if (obj.parent) node.parentId = obj.parent.id;
2014
- else if (obj.isRoot) node.frameId = tree.options.frameId;
2015
- return node;
2016
- }
2017
- function descriptionFor(el, role, nameInfo) {
2018
- const name = nameInfo.value;
2019
- let description = normalizeText(computeAccessibleDescription(el));
2020
- if (nameInfo.fromAuthorAria && el.localName === "summary") {
2021
- const contents = textEquivalent(el);
2022
- if (contents && contents !== name) return contents;
2023
- }
2024
- if (!description && el.localName === "summary" && el.parentElement?.localName !== "details") description = textEquivalent(el);
2025
- if ((!description || description === name) && el instanceof HTMLInputElement && [
2026
- "button",
2027
- "submit",
2028
- "reset"
2029
- ].includes(el.type)) {
2030
- const value = normalizeText(el.getAttribute("value"));
2031
- if (value && value !== name) description = value;
2032
- }
2033
- if (!description || description === name) return void 0;
2034
- return description;
2035
- }
2036
- /** Chromium's BuildProtocolAXNodeForDOMNodeWithNoAXNode: returned when the
2037
- * inspected DOM node has no AX object at all (head, script, …). */
2038
- function noAXNodeFor(registry, domNode) {
2039
- return {
2040
- nodeId: "0",
2041
- ignored: true,
2042
- role: ax("role", "none"),
2043
- chromeRole: chromeRoleValue("None"),
2044
- ignoredReasons: [ignoredReason("notRendered")],
2045
- backendDOMNodeId: registry.backendIdFor(domNode)
2046
- };
2047
- }
2048
- /** Emit all included children; ignored children contribute another layer
2049
- * (follow_ignored), depth-first like Chromium's AddChildren. Returns the
2050
- * emitted objs and the unignored ones among them (the next BFS level). */
2051
- function addChildren(obj) {
2052
- const emitted = [];
2053
- const unignored = [];
2054
- const reachable = [...obj.children];
2055
- while (reachable.length) {
2056
- const descendant = reachable.shift();
2057
- if (!descendant) continue;
2058
- if (descendant.ignored) reachable.unshift(...descendant.children);
2059
- else unignored.push(descendant);
2060
- emitted.push(descendant);
2061
- }
2062
- return {
2063
- emitted,
2064
- unignored
2065
- };
2066
- }
2067
- function nearestObj(tree, domNode) {
2068
- return tree.byNode.get(domNode);
2069
- }
2070
- function includedAncestor(obj) {
2071
- for (let cur = obj.parent; cur; cur = cur.parent) if (cur.included) return cur;
2072
- }
2073
- function getFullAXTree(options, depth) {
2074
- const tree = buildTree(options);
2075
- const maxDepth = depth == null || depth < 0 ? -1 : depth;
2076
- const nodes = [wireNode(tree, tree.root)];
2077
- const queue = [{
2078
- obj: tree.root,
2079
- depth: 1
2080
- }];
2081
- while (queue.length) {
2082
- const item = queue.shift();
2083
- if (!item) continue;
2084
- const { emitted, unignored } = addChildren(item.obj);
2085
- for (const child of emitted) {
2086
- nodes.push(wireNode(tree, child));
2087
- nodes.push(...markerNodes(tree, child));
2088
- }
2089
- for (const child of unignored) if (maxDepth === -1 || item.depth < maxDepth) queue.push({
2090
- obj: child,
2091
- depth: item.depth + 1
2092
- });
2093
- }
2094
- return { nodes };
2095
- }
2096
- /**
2097
- * Fetch the AX node for a DOM node, optionally with its children and ancestor
2098
- * chain (Chromium's getPartialAXTree). With no target, returns the whole tree
2099
- * (back-compat with earlier clients).
2100
- */
2101
- function getPartialAXTree(options, target, fetchRelatives = true) {
2102
- if (target == null) return getFullAXTree(options);
2103
- const tree = buildTree(options);
2104
- const domNode = options.registry.nodeForBackendId(target);
2105
- if (!domNode) return { nodes: [] };
2106
- const obj = nearestObj(tree, domNode);
2107
- const nodes = [];
2108
- if (obj) nodes.push(wireNode(tree, obj));
2109
- else nodes.push(noAXNodeFor(options.registry, domNode));
2110
- if (!fetchRelatives) return { nodes };
2111
- if (obj && !obj.ignored) for (const child of addChildren(obj).emitted) nodes.push(wireNode(tree, child));
2112
- let parent;
2113
- if (obj) parent = obj.included ? obj.parent : includedAncestor(obj) ?? obj.parent;
2114
- else for (let cur = domNode.parentNode; cur; cur = cur.parentNode) {
2115
- parent = nearestObj(tree, cur);
2116
- if (parent) break;
2117
- }
2118
- if (!parent) return { nodes };
2119
- const firstAncestor = wireNode(tree, parent);
2120
- if (!obj || obj.ignored) firstAncestor.childIds = [obj ? obj.id : "0", ...firstAncestor.childIds ?? []];
2121
- nodes.push(firstAncestor);
2122
- for (let cur = parent.parent; cur; cur = cur.parent) if (cur.included) nodes.push(wireNode(tree, cur));
2123
- return { nodes };
2124
- }
2125
- /** The root (RootWebArea) AX node of the document. */
2126
- function getRootAXNode(options) {
2127
- const tree = buildTree(options);
2128
- return { node: wireNode(tree, tree.root) };
2129
- }
2130
- /** The children of the AX node with the given AX id (follow-ignored layering). */
2131
- function getChildAXNodes(options, id) {
2132
- const tree = buildTree(options);
2133
- const obj = tree.byId.get(id);
2134
- if (!obj) throw new Error("Invalid ID");
2135
- const nodes = [];
2136
- for (const child of addChildren(obj).emitted) {
2137
- nodes.push(wireNode(tree, child));
2138
- nodes.push(...markerNodes(tree, child));
2139
- }
2140
- return { nodes };
2141
- }
2142
- /** The AX node for a DOM node together with every ancestor up to the root. */
2143
- function getAXNodeAndAncestors(options, target) {
2144
- const tree = buildTree(options);
2145
- const domNode = options.registry.nodeForBackendId(target);
2146
- if (!domNode) return { nodes: [] };
2147
- const obj = nearestObj(tree, domNode);
2148
- if (!obj) return { nodes: [noAXNodeFor(options.registry, domNode)] };
2149
- const nodes = [wireNode(tree, obj)];
2150
- for (let cur = obj.parent; cur; cur = cur.parent) if (cur.included) nodes.push(wireNode(tree, cur));
2151
- return { nodes };
2152
- }
2153
- /** All AX nodes in a subtree matching the given accessible name and/or role. */
2154
- function queryAXTree(options, query) {
2155
- const tree = buildTree(options);
2156
- let root;
2157
- if (query.target == null) root = tree.root;
2158
- else {
2159
- let domNode = options.registry.nodeForBackendId(query.target);
2160
- if (domNode && domNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) domNode = domNode.host ?? domNode;
2161
- if (domNode?.nodeType === Node.DOCUMENT_NODE) root = tree.root;
2162
- else if (domNode) root = nearestObj(tree, domNode);
2163
- }
2164
- if (!root) return { nodes: [] };
2165
- const matches = [];
2166
- const stack = [root];
2167
- while (stack.length) {
2168
- const obj = stack.pop();
2169
- if (!obj) continue;
2170
- for (let i = obj.children.length - 1; i >= 0; i--) {
2171
- const child = obj.children[i];
2172
- if (child) stack.push(child);
2173
- }
2174
- if (!obj.included) continue;
2175
- if (query.role != null) {
2176
- if ((obj.ignored ? forcedRoleOf(obj).role.value : obj.wireRole) !== query.role) continue;
2177
- }
2178
- if (query.accessibleName != null) {
2179
- if ((obj.ignored ? matchName(tree, obj) : nameOf(tree, obj).value) !== query.accessibleName) continue;
2180
- }
2181
- matches.push(wireNode(tree, obj, true));
2182
- }
2183
- return { nodes: matches };
2184
- }
2185
- //#endregion
2186
- export { createDomRegistry, getAXNodeAndAncestors, getChildAXNodes, getFullAXTree, getPartialAXTree, getRootAXNode, queryAXTree };