@mercuryo-ai/agentbrowse 0.2.61 → 0.2.63
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.
- package/CHANGELOG.md +33 -1
- package/README.md +102 -9
- package/dist/browser-session-state.d.ts +2 -11
- package/dist/browser-session-state.d.ts.map +1 -1
- package/dist/browser-session-state.js +0 -4
- package/dist/commands/act.d.ts.map +1 -1
- package/dist/commands/act.js +14 -5
- package/dist/commands/attach.d.ts +1 -3
- package/dist/commands/attach.d.ts.map +1 -1
- package/dist/commands/attach.js +0 -2
- package/dist/commands/browser-status.d.ts +0 -2
- package/dist/commands/browser-status.d.ts.map +1 -1
- package/dist/commands/browser-status.js +1 -7
- package/dist/commands/interaction-kernel.d.ts +1 -1
- package/dist/commands/interaction-kernel.d.ts.map +1 -1
- package/dist/commands/interaction-kernel.js +1 -1
- package/dist/commands/launch.d.ts +0 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +0 -4
- package/dist/commands/observe-accessibility.d.ts.map +1 -1
- package/dist/commands/observe-accessibility.js +36 -2
- package/dist/commands/observe-inventory.d.ts +49 -7
- package/dist/commands/observe-inventory.d.ts.map +1 -1
- package/dist/commands/observe-inventory.js +807 -96
- package/dist/commands/observe-persistence.d.ts.map +1 -1
- package/dist/commands/observe-persistence.js +49 -6
- package/dist/commands/observe-projection.d.ts +6 -2
- package/dist/commands/observe-projection.d.ts.map +1 -1
- package/dist/commands/observe-projection.js +251 -27
- package/dist/commands/observe-semantics.d.ts +1 -0
- package/dist/commands/observe-semantics.d.ts.map +1 -1
- package/dist/commands/observe-semantics.js +541 -135
- package/dist/commands/observe-signals.d.ts +4 -4
- package/dist/commands/observe-signals.d.ts.map +1 -1
- package/dist/commands/observe-signals.js +2 -2
- package/dist/commands/observe-surfaces.d.ts +2 -1
- package/dist/commands/observe-surfaces.d.ts.map +1 -1
- package/dist/commands/observe-surfaces.js +143 -45
- package/dist/commands/observe.d.ts +5 -1
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +15 -11
- package/dist/commands/semantic-observe.d.ts.map +1 -1
- package/dist/commands/semantic-observe.js +43 -0
- package/dist/library.d.ts +2 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +2 -1
- package/dist/match-resolve-fill.d.ts +196 -0
- package/dist/match-resolve-fill.d.ts.map +1 -0
- package/dist/match-resolve-fill.js +700 -0
- package/dist/match-resolve-fill.test-support.d.ts +34 -0
- package/dist/match-resolve-fill.test-support.d.ts.map +1 -0
- package/dist/match-resolve-fill.test-support.js +81 -0
- package/dist/runtime-protected-state.d.ts.map +1 -1
- package/dist/runtime-protected-state.js +12 -0
- package/dist/runtime-state.d.ts +6 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +6 -0
- package/dist/secrets/form-matcher.d.ts.map +1 -1
- package/dist/secrets/form-matcher.js +76 -27
- package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -1
- package/dist/secrets/protected-exact-value-redaction.js +6 -0
- package/dist/secrets/protected-fill.js +3 -3
- package/dist/session.d.ts +3 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -2
- package/dist/solver/browser-launcher.d.ts.map +1 -1
- package/dist/solver/browser-launcher.js +2 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/docs/README.md +28 -11
- package/docs/api-reference.md +311 -19
- package/docs/assistive-runtime.md +41 -16
- package/docs/getting-started.md +45 -1
- package/docs/integration-checklist.md +32 -3
- package/docs/match-resolve-fill.md +699 -0
- package/docs/protected-fill.md +373 -91
- package/docs/testing.md +147 -15
- package/docs/troubleshooting.md +5 -0
- package/examples/README.md +7 -0
- package/examples/match-resolve-fill.ts +107 -0
- package/package.json +4 -2
|
@@ -14,10 +14,13 @@ export function inferStructuredCellVariantFromEvidence(evidence) {
|
|
|
14
14
|
const hasSeatMetadata = Boolean(evidence.hasSeatAttribute || evidence.hasSeatRowAttribute || evidence.hasSeatColumnAttribute);
|
|
15
15
|
const seatIdentityLike = /(?:\bseat\s*\d{1,3}[a-z]?\b|место\s*\d{1,3}[a-z]?\b|\b\d{1,3}[a-z]\b|\b[a-z]\d{1,3}\b)/i.test(normalizedLabel);
|
|
16
16
|
const seatClassLike = /seat|cabin|fare|row/.test(className);
|
|
17
|
-
const compactDateCellLabel =
|
|
17
|
+
const compactDateCellLabel = /^\d{1,2}$/.test(normalizedLabel) ||
|
|
18
|
+
/^(?:\d{1,2}[./-]\d{1,2}(?:[./-]\d{2,4})?)$/.test(normalizedLabel);
|
|
19
|
+
const leadingNumericDayToken = /^\d{1,2}\b/.test(normalizedLabel);
|
|
18
20
|
const dateLike = surfaceKind === 'datepicker' ||
|
|
19
21
|
(hasDateMetadata && role === 'gridcell') ||
|
|
20
|
-
((role === 'gridcell' || structuredSurface || hasDateMetadata) &&
|
|
22
|
+
((role === 'gridcell' || structuredSurface || hasDateMetadata) &&
|
|
23
|
+
(compactDateCellLabel || (hasDateMetadata && leadingNumericDayToken)));
|
|
21
24
|
if (dateLike) {
|
|
22
25
|
return 'date-cell';
|
|
23
26
|
}
|
|
@@ -415,14 +418,16 @@ export function normalizeStagehandSelector(selector) {
|
|
|
415
418
|
}
|
|
416
419
|
: { selector };
|
|
417
420
|
}
|
|
418
|
-
async function
|
|
421
|
+
async function collectDomTargetsFromDocumentRaw(context, options) {
|
|
419
422
|
const includeActivationAffordances = options?.includeActivationAffordances === true;
|
|
423
|
+
const debugStructuralProfileStats = options?.debugStructuralProfileStats === true;
|
|
420
424
|
const inheritedFramePath = JSON.stringify(options?.framePath ?? []);
|
|
421
425
|
const inheritedFrameUrl = JSON.stringify(options?.frameUrl ?? '');
|
|
422
426
|
const inheritedPageSignature = JSON.stringify(options?.pageSignature ?? '');
|
|
423
427
|
const inheritedPageFormSelector = JSON.stringify(options?.pageFormSelector ?? '');
|
|
424
|
-
const
|
|
428
|
+
const observedPayload = await context.evaluate(String.raw `(() => {
|
|
425
429
|
const includeActivationAffordances = ${includeActivationAffordances ? 'true' : 'false'};
|
|
430
|
+
const debugStructuralProfileStats = ${debugStructuralProfileStats ? 'true' : 'false'};
|
|
426
431
|
const inheritedFramePath = ${inheritedFramePath};
|
|
427
432
|
const inheritedFrameUrl = ${inheritedFrameUrl};
|
|
428
433
|
const inheritedPageSignature = ${inheritedPageSignature};
|
|
@@ -1108,7 +1113,26 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
1108
1113
|
return inferRole(element) || tag;
|
|
1109
1114
|
};
|
|
1110
1115
|
|
|
1116
|
+
const selectorCache = new WeakMap();
|
|
1117
|
+
|
|
1111
1118
|
const buildSelector = (element) => {
|
|
1119
|
+
if (!isHTMLElementNode(element)) {
|
|
1120
|
+
return undefined;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const cachedSelector = selectorCache.get(element);
|
|
1124
|
+
if (typeof cachedSelector === 'string') {
|
|
1125
|
+
return cachedSelector;
|
|
1126
|
+
}
|
|
1127
|
+
if (cachedSelector === null) {
|
|
1128
|
+
return undefined;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const finalizeSelector = (value) => {
|
|
1132
|
+
selectorCache.set(element, value ?? null);
|
|
1133
|
+
return value;
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1112
1136
|
const testIdAttributeOf = (candidate) => {
|
|
1113
1137
|
if (candidate.hasAttribute('data-testid')) return 'data-testid';
|
|
1114
1138
|
if (candidate.hasAttribute('data-test-id')) return 'data-test-id';
|
|
@@ -1135,7 +1159,7 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
1135
1159
|
};
|
|
1136
1160
|
|
|
1137
1161
|
if (element.id && isSelectorUniqueFor(element, '#' + cssEscape(element.id))) {
|
|
1138
|
-
return '#' + cssEscape(element.id);
|
|
1162
|
+
return finalizeSelector('#' + cssEscape(element.id));
|
|
1139
1163
|
}
|
|
1140
1164
|
|
|
1141
1165
|
const testId =
|
|
@@ -1143,7 +1167,7 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
1143
1167
|
if (testId) {
|
|
1144
1168
|
const selectorValue = testIdSelectorOf(element, testId);
|
|
1145
1169
|
if (isSelectorUniqueFor(element, selectorValue)) {
|
|
1146
|
-
return selectorValue;
|
|
1170
|
+
return finalizeSelector(selectorValue);
|
|
1147
1171
|
}
|
|
1148
1172
|
}
|
|
1149
1173
|
|
|
@@ -1152,7 +1176,7 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
1152
1176
|
if (name) {
|
|
1153
1177
|
const selectorValue = tag + '[name="' + cssEscape(name) + '"]';
|
|
1154
1178
|
if (isSelectorUniqueFor(element, selectorValue)) {
|
|
1155
|
-
return selectorValue;
|
|
1179
|
+
return finalizeSelector(selectorValue);
|
|
1156
1180
|
}
|
|
1157
1181
|
}
|
|
1158
1182
|
|
|
@@ -1201,10 +1225,14 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
1201
1225
|
}
|
|
1202
1226
|
current = current.parentElement;
|
|
1203
1227
|
}
|
|
1204
|
-
if (path.length === 0) return undefined;
|
|
1228
|
+
if (path.length === 0) return finalizeSelector(undefined);
|
|
1205
1229
|
|
|
1206
1230
|
const structuralSelector = path.join(' > ');
|
|
1207
|
-
return
|
|
1231
|
+
return finalizeSelector(
|
|
1232
|
+
isSelectorUniqueFor(element, structuralSelector)
|
|
1233
|
+
? structuralSelector
|
|
1234
|
+
: undefined
|
|
1235
|
+
);
|
|
1208
1236
|
};
|
|
1209
1237
|
|
|
1210
1238
|
const selectorFromRelation = (element, attribute) => {
|
|
@@ -1353,10 +1381,44 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
1353
1381
|
return false;
|
|
1354
1382
|
};
|
|
1355
1383
|
|
|
1384
|
+
const visibleInteractiveDescendantCountCache = new WeakMap();
|
|
1385
|
+
|
|
1356
1386
|
const visibleInteractiveDescendantCountOf = (element) => {
|
|
1357
|
-
|
|
1387
|
+
if (!isHTMLElementNode(element)) {
|
|
1388
|
+
return 0;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const cachedCount = visibleInteractiveDescendantCountCache.get(element);
|
|
1392
|
+
if (typeof cachedCount === 'number') {
|
|
1393
|
+
return cachedCount;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
const count = Array.from(element.querySelectorAll(selector)).filter((candidate) => {
|
|
1358
1397
|
return isHTMLElementNode(candidate) && isVisible(candidate);
|
|
1359
1398
|
}).length;
|
|
1399
|
+
visibleInteractiveDescendantCountCache.set(element, count);
|
|
1400
|
+
return count;
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
const descendantEditableCountCache = new WeakMap();
|
|
1404
|
+
|
|
1405
|
+
const descendantEditableCountOf = (element) => {
|
|
1406
|
+
if (!isHTMLElementNode(element)) {
|
|
1407
|
+
return 0;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const cachedCount = descendantEditableCountCache.get(element);
|
|
1411
|
+
if (typeof cachedCount === 'number') {
|
|
1412
|
+
return cachedCount;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
const count = Array.from(
|
|
1416
|
+
element.querySelectorAll(
|
|
1417
|
+
'input:not([type="hidden"]), textarea, select, [contenteditable="true"]'
|
|
1418
|
+
)
|
|
1419
|
+
).filter((candidate) => isHTMLElementNode(candidate) && isVisible(candidate)).length;
|
|
1420
|
+
descendantEditableCountCache.set(element, count);
|
|
1421
|
+
return count;
|
|
1360
1422
|
};
|
|
1361
1423
|
|
|
1362
1424
|
const clickableSemanticBlobOf = (element) => {
|
|
@@ -2230,6 +2292,87 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2230
2292
|
return Object.keys(states).length > 0 ? states : undefined;
|
|
2231
2293
|
};
|
|
2232
2294
|
|
|
2295
|
+
const canonicalDateIdentityFromParts = (yearValue, monthValue, dayValue) => {
|
|
2296
|
+
const year = Number.parseInt(String(yearValue || '').trim(), 10);
|
|
2297
|
+
const month = Number.parseInt(String(monthValue || '').trim(), 10);
|
|
2298
|
+
const day = Number.parseInt(String(dayValue || '').trim(), 10);
|
|
2299
|
+
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
|
|
2300
|
+
return undefined;
|
|
2301
|
+
}
|
|
2302
|
+
if (month < 1 || month > 12 || day < 1 || day > 31) {
|
|
2303
|
+
return undefined;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
const candidate = new Date(Date.UTC(year, month - 1, day));
|
|
2307
|
+
if (
|
|
2308
|
+
candidate.getUTCFullYear() !== year ||
|
|
2309
|
+
candidate.getUTCMonth() !== month - 1 ||
|
|
2310
|
+
candidate.getUTCDate() !== day
|
|
2311
|
+
) {
|
|
2312
|
+
return undefined;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
return {
|
|
2316
|
+
dateIso:
|
|
2317
|
+
year.toString().padStart(4, '0') +
|
|
2318
|
+
'-' +
|
|
2319
|
+
month.toString().padStart(2, '0') +
|
|
2320
|
+
'-' +
|
|
2321
|
+
day.toString().padStart(2, '0'),
|
|
2322
|
+
dateYear: year,
|
|
2323
|
+
dateMonth: month,
|
|
2324
|
+
dateDay: day,
|
|
2325
|
+
};
|
|
2326
|
+
};
|
|
2327
|
+
|
|
2328
|
+
const canonicalDateIdentityFromValue = (value) => {
|
|
2329
|
+
const normalized = normalizeDescriptorText(value || '');
|
|
2330
|
+
const match = normalized?.match(/\b(\d{4})-(\d{1,2})-(\d{1,2})\b/);
|
|
2331
|
+
if (!match) {
|
|
2332
|
+
return undefined;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
return canonicalDateIdentityFromParts(match[1], match[2], match[3]);
|
|
2336
|
+
};
|
|
2337
|
+
|
|
2338
|
+
const canonicalDateIdentityOf = (element, normalizedLabel) => {
|
|
2339
|
+
const metadataValues = [
|
|
2340
|
+
element.getAttribute('data-iso'),
|
|
2341
|
+
element.getAttribute('data-date'),
|
|
2342
|
+
element.getAttribute('data-day'),
|
|
2343
|
+
element.getAttribute('data-testid'),
|
|
2344
|
+
element.getAttribute('data-test-id'),
|
|
2345
|
+
element.getAttribute('id'),
|
|
2346
|
+
element.getAttribute('class'),
|
|
2347
|
+
element.getAttribute('title'),
|
|
2348
|
+
element.getAttribute('aria-label'),
|
|
2349
|
+
];
|
|
2350
|
+
|
|
2351
|
+
for (const candidateValue of metadataValues) {
|
|
2352
|
+
const directIdentity = canonicalDateIdentityFromValue(candidateValue);
|
|
2353
|
+
if (directIdentity) {
|
|
2354
|
+
return directIdentity;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
const leadingDayToken = normalizedLabel.match(/^(\d{1,2})\b/)?.[1];
|
|
2359
|
+
const dayValue = element.getAttribute('data-day')?.trim() || leadingDayToken;
|
|
2360
|
+
const monthValue =
|
|
2361
|
+
element.getAttribute('data-month')?.trim() ||
|
|
2362
|
+
element.getAttribute('data-date-month')?.trim() ||
|
|
2363
|
+
composedClosest(element, '[data-month], [data-date-month]')?.getAttribute?.('data-month')?.trim() ||
|
|
2364
|
+
composedClosest(element, '[data-month], [data-date-month]')?.getAttribute?.('data-date-month')?.trim() ||
|
|
2365
|
+
undefined;
|
|
2366
|
+
const yearValue =
|
|
2367
|
+
element.getAttribute('data-year')?.trim() ||
|
|
2368
|
+
element.getAttribute('data-date-year')?.trim() ||
|
|
2369
|
+
composedClosest(element, '[data-year], [data-date-year]')?.getAttribute?.('data-year')?.trim() ||
|
|
2370
|
+
composedClosest(element, '[data-year], [data-date-year]')?.getAttribute?.('data-date-year')?.trim() ||
|
|
2371
|
+
undefined;
|
|
2372
|
+
|
|
2373
|
+
return canonicalDateIdentityFromParts(yearValue, monthValue, dayValue);
|
|
2374
|
+
};
|
|
2375
|
+
|
|
2233
2376
|
const inferStructuredCell = (element, surface) => {
|
|
2234
2377
|
if (!isHTMLElementNode(element)) return undefined;
|
|
2235
2378
|
|
|
@@ -2243,10 +2386,22 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2243
2386
|
const label =
|
|
2244
2387
|
explicitLabelOf(element) || textOf(element) || seatValueLabel || syntheticLabelOf(element) || '';
|
|
2245
2388
|
const normalizedLabel = label.replace(/\s+/g, ' ').trim();
|
|
2389
|
+
const dateIdentity = canonicalDateIdentityOf(element, normalizedLabel);
|
|
2246
2390
|
const explicitDateCellMetadata =
|
|
2391
|
+
Boolean(dateIdentity) ||
|
|
2247
2392
|
element.hasAttribute('data-day') ||
|
|
2248
2393
|
element.hasAttribute('data-date') ||
|
|
2249
2394
|
element.hasAttribute('data-iso') ||
|
|
2395
|
+
/\bdate[-_ ]?cell\b|\bcalendar[-_ ]?day\b|\b\d{4}-\d{2}-\d{2}\b/i.test(
|
|
2396
|
+
[
|
|
2397
|
+
element.getAttribute('data-testid') || '',
|
|
2398
|
+
element.getAttribute('data-test-id') || '',
|
|
2399
|
+
element.getAttribute('id') || '',
|
|
2400
|
+
element.getAttribute('class') || '',
|
|
2401
|
+
]
|
|
2402
|
+
.join(' ')
|
|
2403
|
+
.trim()
|
|
2404
|
+
) ||
|
|
2250
2405
|
element.hasAttribute('aria-rowindex') ||
|
|
2251
2406
|
element.hasAttribute('aria-colindex') ||
|
|
2252
2407
|
Boolean(
|
|
@@ -2291,6 +2446,7 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2291
2446
|
return {
|
|
2292
2447
|
family: 'structured-grid',
|
|
2293
2448
|
variant: structuredCellVariant,
|
|
2449
|
+
...(dateIdentity ?? {}),
|
|
2294
2450
|
row,
|
|
2295
2451
|
column,
|
|
2296
2452
|
zone,
|
|
@@ -2369,9 +2525,19 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2369
2525
|
return undefined;
|
|
2370
2526
|
};
|
|
2371
2527
|
|
|
2528
|
+
const surfacePositionTraitsCache = new WeakMap();
|
|
2529
|
+
|
|
2372
2530
|
const surfacePositionTraitsOf = (surface) => {
|
|
2373
2531
|
if (!isHTMLElementNode(surface) || !isVisible(surface)) return undefined;
|
|
2374
2532
|
|
|
2533
|
+
const cachedTraits = surfacePositionTraitsCache.get(surface);
|
|
2534
|
+
if (cachedTraits) {
|
|
2535
|
+
return cachedTraits;
|
|
2536
|
+
}
|
|
2537
|
+
if (cachedTraits === null) {
|
|
2538
|
+
return undefined;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2375
2541
|
const style = window.getComputedStyle(surface);
|
|
2376
2542
|
const position = (style.position || '').toLowerCase();
|
|
2377
2543
|
const rect = surface.getBoundingClientRect();
|
|
@@ -2390,19 +2556,26 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2390
2556
|
style.backgroundColor !== 'rgba(0, 0, 0, 0)');
|
|
2391
2557
|
|
|
2392
2558
|
if (rect.width < 180 || rect.height < 72 || interactiveCount < 1 || coverage > 0.45) {
|
|
2559
|
+
surfacePositionTraitsCache.set(surface, null);
|
|
2393
2560
|
return undefined;
|
|
2394
2561
|
}
|
|
2395
2562
|
|
|
2396
2563
|
if (position === 'fixed') {
|
|
2397
|
-
|
|
2564
|
+
const traits = { kind: 'floating-panel', priority: 92 };
|
|
2565
|
+
surfacePositionTraitsCache.set(surface, traits);
|
|
2566
|
+
return traits;
|
|
2398
2567
|
}
|
|
2399
2568
|
|
|
2400
2569
|
if (position === 'sticky') {
|
|
2401
|
-
|
|
2570
|
+
const traits = { kind: 'sticky-panel', priority: 88 };
|
|
2571
|
+
surfacePositionTraitsCache.set(surface, traits);
|
|
2572
|
+
return traits;
|
|
2402
2573
|
}
|
|
2403
2574
|
|
|
2404
2575
|
if (position === 'absolute' && hasCardChrome && zIndexValue > 0) {
|
|
2405
|
-
|
|
2576
|
+
const traits = { kind: 'floating-panel', priority: 82 };
|
|
2577
|
+
surfacePositionTraitsCache.set(surface, traits);
|
|
2578
|
+
return traits;
|
|
2406
2579
|
}
|
|
2407
2580
|
|
|
2408
2581
|
if (
|
|
@@ -2411,33 +2584,67 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2411
2584
|
interactiveCount >= 1 &&
|
|
2412
2585
|
(modalBackdropAncestorOf(surface) || siblingModalBackdropOf(surface))
|
|
2413
2586
|
) {
|
|
2414
|
-
|
|
2587
|
+
const traits = { kind: 'floating-panel', priority: 90 };
|
|
2588
|
+
surfacePositionTraitsCache.set(surface, traits);
|
|
2589
|
+
return traits;
|
|
2415
2590
|
}
|
|
2416
2591
|
|
|
2592
|
+
surfacePositionTraitsCache.set(surface, null);
|
|
2417
2593
|
return undefined;
|
|
2418
2594
|
};
|
|
2419
2595
|
|
|
2596
|
+
const inferredSurfaceKindCache = new WeakMap();
|
|
2597
|
+
|
|
2420
2598
|
const inferSurfaceKind = (surface) => {
|
|
2421
2599
|
if (!isHTMLElementNode(surface)) return undefined;
|
|
2600
|
+
|
|
2601
|
+
const cachedSurfaceKind = inferredSurfaceKindCache.get(surface);
|
|
2602
|
+
if (typeof cachedSurfaceKind === 'string') {
|
|
2603
|
+
return cachedSurfaceKind;
|
|
2604
|
+
}
|
|
2605
|
+
if (cachedSurfaceKind === null) {
|
|
2606
|
+
return undefined;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2422
2609
|
const positionedSurface = surfacePositionTraitsOf(surface);
|
|
2423
|
-
if (positionedSurface?.kind)
|
|
2610
|
+
if (positionedSurface?.kind) {
|
|
2611
|
+
inferredSurfaceKindCache.set(surface, positionedSurface.kind);
|
|
2612
|
+
return positionedSurface.kind;
|
|
2613
|
+
}
|
|
2424
2614
|
const role = surface.getAttribute('role')?.trim();
|
|
2425
|
-
if (role === 'dialog') return 'dialog';
|
|
2426
|
-
if (role === 'listbox') return 'listbox';
|
|
2427
|
-
if (role === 'menu') return 'menu';
|
|
2428
|
-
if (role === 'grid') return 'grid';
|
|
2429
|
-
if (role === 'tabpanel') return 'tabpanel';
|
|
2430
|
-
if (isGenericClickableElement(surface) && isStructuredContainer(surface))
|
|
2615
|
+
if (role === 'dialog') return inferredSurfaceKindCache.set(surface, 'dialog'), 'dialog';
|
|
2616
|
+
if (role === 'listbox') return inferredSurfaceKindCache.set(surface, 'listbox'), 'listbox';
|
|
2617
|
+
if (role === 'menu') return inferredSurfaceKindCache.set(surface, 'menu'), 'menu';
|
|
2618
|
+
if (role === 'grid') return inferredSurfaceKindCache.set(surface, 'grid'), 'grid';
|
|
2619
|
+
if (role === 'tabpanel') return inferredSurfaceKindCache.set(surface, 'tabpanel'), 'tabpanel';
|
|
2620
|
+
if (isGenericClickableElement(surface) && isStructuredContainer(surface)) {
|
|
2621
|
+
inferredSurfaceKindCache.set(surface, 'card');
|
|
2622
|
+
return 'card';
|
|
2623
|
+
}
|
|
2431
2624
|
const className = (surface.getAttribute('class') || '').toLowerCase();
|
|
2432
|
-
if (className.includes('calendar') || className.includes('datepicker'))
|
|
2433
|
-
|
|
2434
|
-
|
|
2625
|
+
if (className.includes('calendar') || className.includes('datepicker')) {
|
|
2626
|
+
inferredSurfaceKindCache.set(surface, 'datepicker');
|
|
2627
|
+
return 'datepicker';
|
|
2628
|
+
}
|
|
2629
|
+
if (className.includes('popover')) {
|
|
2630
|
+
inferredSurfaceKindCache.set(surface, 'popover');
|
|
2631
|
+
return 'popover';
|
|
2632
|
+
}
|
|
2633
|
+
if (className.includes('dropdown')) {
|
|
2634
|
+
inferredSurfaceKindCache.set(surface, 'dropdown');
|
|
2635
|
+
return 'dropdown';
|
|
2636
|
+
}
|
|
2435
2637
|
const tag = surface.tagName.toLowerCase();
|
|
2436
|
-
if (tag === 'article') return 'card';
|
|
2437
|
-
if (tag === 'fieldset') return 'group';
|
|
2438
|
-
if (tag === 'li') return 'listitem';
|
|
2439
|
-
if (tag === 'section' || tag === 'form' || tag === 'aside')
|
|
2440
|
-
|
|
2638
|
+
if (tag === 'article') return inferredSurfaceKindCache.set(surface, 'card'), 'card';
|
|
2639
|
+
if (tag === 'fieldset') return inferredSurfaceKindCache.set(surface, 'group'), 'group';
|
|
2640
|
+
if (tag === 'li') return inferredSurfaceKindCache.set(surface, 'listitem'), 'listitem';
|
|
2641
|
+
if (tag === 'section' || tag === 'form' || tag === 'aside') {
|
|
2642
|
+
inferredSurfaceKindCache.set(surface, tag);
|
|
2643
|
+
return tag;
|
|
2644
|
+
}
|
|
2645
|
+
const resolvedSurfaceKind = role || surface.tagName.toLowerCase();
|
|
2646
|
+
inferredSurfaceKindCache.set(surface, resolvedSurfaceKind || null);
|
|
2647
|
+
return resolvedSurfaceKind;
|
|
2441
2648
|
};
|
|
2442
2649
|
|
|
2443
2650
|
const labelledByTextOf = (element) => {
|
|
@@ -2611,15 +2818,29 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2611
2818
|
return text.length <= 140 ? text : text.slice(0, 140);
|
|
2612
2819
|
};
|
|
2613
2820
|
|
|
2821
|
+
const contextNodeCache = new WeakMap();
|
|
2822
|
+
|
|
2614
2823
|
const contextNodeOf = (element) => {
|
|
2615
|
-
if (!element) return undefined;
|
|
2824
|
+
if (!isHTMLElementNode(element)) return undefined;
|
|
2616
2825
|
|
|
2617
|
-
const
|
|
2826
|
+
const cachedNode = contextNodeCache.get(element);
|
|
2827
|
+
if (cachedNode) {
|
|
2828
|
+
return cachedNode;
|
|
2829
|
+
}
|
|
2830
|
+
if (cachedNode === null) {
|
|
2831
|
+
return undefined;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
const kind = element.getAttribute('role')?.trim() || element.tagName?.toLowerCase?.();
|
|
2618
2835
|
const fallbackLabel = contextLabelOf(element);
|
|
2619
2836
|
const text = contextTextOf(element);
|
|
2620
2837
|
const selector = buildSelector(element);
|
|
2621
|
-
if (!kind && !fallbackLabel && !text && !selector)
|
|
2622
|
-
|
|
2838
|
+
if (!kind && !fallbackLabel && !text && !selector) {
|
|
2839
|
+
contextNodeCache.set(element, null);
|
|
2840
|
+
return undefined;
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
const contextNode = {
|
|
2623
2844
|
kind: kind || undefined,
|
|
2624
2845
|
label: fallbackLabel,
|
|
2625
2846
|
text:
|
|
@@ -2629,6 +2850,8 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2629
2850
|
selector,
|
|
2630
2851
|
fallbackLabel,
|
|
2631
2852
|
};
|
|
2853
|
+
contextNodeCache.set(element, contextNode);
|
|
2854
|
+
return contextNode;
|
|
2632
2855
|
};
|
|
2633
2856
|
|
|
2634
2857
|
const pageSignature =
|
|
@@ -2646,6 +2869,23 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2646
2869
|
const seenElements = new Set();
|
|
2647
2870
|
const overlaySurfaceSelector =
|
|
2648
2871
|
'[role="dialog"], [aria-modal="true"], [role="listbox"], [role="menu"], [role="grid"], [role="tabpanel"], [class*="popover"], [class*="dropdown"], [class*="listbox"], [class*="calendar"], [class*="datepicker"]';
|
|
2872
|
+
// Keep structural scans bounded without assuming shallow wrapper trees.
|
|
2873
|
+
const structuralAncestorSearchDepth = 16;
|
|
2874
|
+
const structuralProfileDebugStats = debugStructuralProfileStats
|
|
2875
|
+
? {
|
|
2876
|
+
structuralProfileBuilds: 0,
|
|
2877
|
+
ownerCandidateAncestorVisits: 0,
|
|
2878
|
+
explicitSurfaceAncestorVisits: 0,
|
|
2879
|
+
surfaceSelectorAncestorVisits: 0,
|
|
2880
|
+
}
|
|
2881
|
+
: undefined;
|
|
2882
|
+
const structuralProfileCache = new WeakMap();
|
|
2883
|
+
|
|
2884
|
+
const incrementStructuralProfileDebugStat = (key) => {
|
|
2885
|
+
if (structuralProfileDebugStats) {
|
|
2886
|
+
structuralProfileDebugStats[key] += 1;
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2649
2889
|
|
|
2650
2890
|
const compactText = (value) => (value || '').replace(/\s+/g, ' ').trim();
|
|
2651
2891
|
|
|
@@ -2867,7 +3107,7 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
2867
3107
|
let preferredFallback = undefined;
|
|
2868
3108
|
let current = surface;
|
|
2869
3109
|
let depth = 0;
|
|
2870
|
-
while (current && depth <
|
|
3110
|
+
while (current && depth < structuralAncestorSearchDepth) {
|
|
2871
3111
|
if (!isHTMLElementNode(current) || !isVisible(current)) {
|
|
2872
3112
|
current = current?.parentElement ?? null;
|
|
2873
3113
|
depth += 1;
|
|
@@ -3138,7 +3378,7 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3138
3378
|
for (const tokenNode of tokenNodes) {
|
|
3139
3379
|
let current = composedParentElement(tokenNode);
|
|
3140
3380
|
let depth = 0;
|
|
3141
|
-
while (current && depth <
|
|
3381
|
+
while (current && depth < structuralAncestorSearchDepth) {
|
|
3142
3382
|
if (isPotentialVisualGridSurface(current)) {
|
|
3143
3383
|
candidateSurfaces.set(
|
|
3144
3384
|
current,
|
|
@@ -3190,66 +3430,67 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3190
3430
|
.filter((element) => isHTMLElementNode(element))
|
|
3191
3431
|
.filter((element) => isVisible(element));
|
|
3192
3432
|
|
|
3193
|
-
const
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
!isHTMLElementNode(candidate) ||
|
|
3198
|
-
candidate === element ||
|
|
3199
|
-
!isVisible(candidate)
|
|
3200
|
-
) {
|
|
3201
|
-
return;
|
|
3202
|
-
}
|
|
3433
|
+
const explicitSurfaceKindOf = (surface) => {
|
|
3434
|
+
if (!isHTMLElementNode(surface)) {
|
|
3435
|
+
return undefined;
|
|
3436
|
+
}
|
|
3203
3437
|
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3438
|
+
const positionedSurface = surfacePositionTraitsOf(surface);
|
|
3439
|
+
if (positionedSurface?.kind) {
|
|
3440
|
+
return positionedSurface.kind;
|
|
3441
|
+
}
|
|
3208
3442
|
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3443
|
+
const role = surface.getAttribute('role')?.trim() || '';
|
|
3444
|
+
if (role === 'dialog') return 'dialog';
|
|
3445
|
+
if (role === 'listbox') return 'listbox';
|
|
3446
|
+
if (role === 'menu') return 'menu';
|
|
3447
|
+
if (role === 'grid') return 'grid';
|
|
3448
|
+
if (role === 'tabpanel') return 'tabpanel';
|
|
3212
3449
|
|
|
3213
|
-
|
|
3214
|
-
|
|
3450
|
+
const className = (surface.getAttribute('class') || '').toLowerCase();
|
|
3451
|
+
if (className.includes('calendar') || className.includes('datepicker')) return 'datepicker';
|
|
3452
|
+
if (className.includes('popover')) return 'popover';
|
|
3453
|
+
if (className.includes('dropdown')) return 'dropdown';
|
|
3215
3454
|
|
|
3216
|
-
|
|
3217
|
-
|
|
3455
|
+
const tag = surface.tagName.toLowerCase();
|
|
3456
|
+
if (tag === 'form') return 'form';
|
|
3457
|
+
return undefined;
|
|
3458
|
+
};
|
|
3218
3459
|
|
|
3460
|
+
const explicitSurfaceOf = (element) => {
|
|
3219
3461
|
let current = composedParentElement(element);
|
|
3220
3462
|
let depth = 0;
|
|
3221
|
-
|
|
3222
|
-
|
|
3463
|
+
|
|
3464
|
+
while (current && depth < structuralAncestorSearchDepth) {
|
|
3465
|
+
incrementStructuralProfileDebugStat('explicitSurfaceAncestorVisits');
|
|
3466
|
+
if (structuralProfileOf(current)?.explicitSurfaceKind) {
|
|
3467
|
+
return current;
|
|
3468
|
+
}
|
|
3223
3469
|
current = composedParentElement(current);
|
|
3224
3470
|
depth += 1;
|
|
3225
3471
|
}
|
|
3226
3472
|
|
|
3227
|
-
|
|
3228
|
-
if (left.priority !== right.priority) {
|
|
3229
|
-
return right.priority - left.priority;
|
|
3230
|
-
}
|
|
3231
|
-
return left.depth - right.depth;
|
|
3232
|
-
});
|
|
3233
|
-
|
|
3234
|
-
return ranked[0]?.element;
|
|
3473
|
+
return undefined;
|
|
3235
3474
|
};
|
|
3236
3475
|
|
|
3237
|
-
const surfaceSelectorsOf = (element,
|
|
3476
|
+
const surfaceSelectorsOf = (element, explicitSurface) => {
|
|
3238
3477
|
const selectors = [];
|
|
3239
|
-
let current = element
|
|
3478
|
+
let current = composedParentElement(element);
|
|
3240
3479
|
|
|
3241
3480
|
while (current) {
|
|
3242
|
-
|
|
3243
|
-
|
|
3481
|
+
incrementStructuralProfileDebugStat('surfaceSelectorAncestorVisits');
|
|
3482
|
+
const currentProfile = structuralProfileOf(current);
|
|
3483
|
+
if (currentProfile?.explicitSurfaceKind) {
|
|
3484
|
+
const selector = currentProfile.selector;
|
|
3244
3485
|
if (selector && !selectors.includes(selector)) {
|
|
3245
3486
|
selectors.push(selector);
|
|
3246
3487
|
}
|
|
3247
3488
|
}
|
|
3248
|
-
current = current
|
|
3489
|
+
current = composedParentElement(current);
|
|
3249
3490
|
}
|
|
3250
3491
|
|
|
3251
|
-
if (
|
|
3252
|
-
const selector =
|
|
3492
|
+
if (explicitSurface) {
|
|
3493
|
+
const selector = structuralProfileOf(explicitSurface)?.selector;
|
|
3253
3494
|
if (selector && !selectors.includes(selector)) {
|
|
3254
3495
|
selectors.push(selector);
|
|
3255
3496
|
}
|
|
@@ -3258,6 +3499,413 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3258
3499
|
return selectors.length > 0 ? selectors : undefined;
|
|
3259
3500
|
};
|
|
3260
3501
|
|
|
3502
|
+
const directContextTextProfileCache = new WeakMap();
|
|
3503
|
+
|
|
3504
|
+
const directContextTextProfileOf = (candidate) => {
|
|
3505
|
+
if (!isHTMLElementNode(candidate)) {
|
|
3506
|
+
return undefined;
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
const cachedProfile = directContextTextProfileCache.get(candidate);
|
|
3510
|
+
if (cachedProfile) {
|
|
3511
|
+
return cachedProfile;
|
|
3512
|
+
}
|
|
3513
|
+
if (cachedProfile === null) {
|
|
3514
|
+
return undefined;
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
const children = [];
|
|
3518
|
+
for (const child of Array.from(candidate.children).slice(0, 12)) {
|
|
3519
|
+
if (!isHTMLElementNode(child) || !isVisible(child)) {
|
|
3520
|
+
continue;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
const tag = child.tagName.toLowerCase();
|
|
3524
|
+
if (
|
|
3525
|
+
['button', 'input', 'textarea', 'select', 'a', 'svg', 'img'].includes(tag) ||
|
|
3526
|
+
child.matches?.(selector)
|
|
3527
|
+
) {
|
|
3528
|
+
continue;
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
const normalized = normalizeDescriptorText(textOf(child, { container: true }));
|
|
3532
|
+
if (!normalized) {
|
|
3533
|
+
continue;
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
const semanticText = normalized
|
|
3537
|
+
.replace(observedLooseActionTextRe, ' ')
|
|
3538
|
+
.replace(/\s+/g, ' ')
|
|
3539
|
+
.trim();
|
|
3540
|
+
if (!semanticText || semanticText.length < 2) {
|
|
3541
|
+
continue;
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
children.push(child);
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
const profile = {
|
|
3548
|
+
count: children.length,
|
|
3549
|
+
children,
|
|
3550
|
+
};
|
|
3551
|
+
directContextTextProfileCache.set(candidate, profile);
|
|
3552
|
+
return profile;
|
|
3553
|
+
};
|
|
3554
|
+
|
|
3555
|
+
const directContextTextBlockCountOf = (candidate, excludedTarget) => {
|
|
3556
|
+
const profile = directContextTextProfileOf(candidate);
|
|
3557
|
+
if (!profile) {
|
|
3558
|
+
return 0;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
if (
|
|
3562
|
+
isHTMLElementNode(excludedTarget) &&
|
|
3563
|
+
profile.children.includes(excludedTarget)
|
|
3564
|
+
) {
|
|
3565
|
+
return Math.max(0, profile.count - 1);
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
return profile.count;
|
|
3569
|
+
};
|
|
3570
|
+
|
|
3571
|
+
const ownerCandidateShapeKeyCache = new WeakMap();
|
|
3572
|
+
|
|
3573
|
+
const ownerCandidateShapeKeyOf = (candidate) => {
|
|
3574
|
+
const cachedShapeKey = ownerCandidateShapeKeyCache.get(candidate);
|
|
3575
|
+
if (typeof cachedShapeKey === 'string') {
|
|
3576
|
+
return cachedShapeKey;
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
const tag = candidate.tagName.toLowerCase();
|
|
3580
|
+
const role = candidate.getAttribute('role')?.trim().toLowerCase() || '';
|
|
3581
|
+
const className = (candidate.getAttribute('class') || '')
|
|
3582
|
+
.trim()
|
|
3583
|
+
.toLowerCase()
|
|
3584
|
+
.split(/\s+/)
|
|
3585
|
+
.filter(Boolean)
|
|
3586
|
+
.sort()
|
|
3587
|
+
.join('.');
|
|
3588
|
+
const shapeKey = tag + '|' + role + '|' + className;
|
|
3589
|
+
ownerCandidateShapeKeyCache.set(candidate, shapeKey);
|
|
3590
|
+
return shapeKey;
|
|
3591
|
+
};
|
|
3592
|
+
|
|
3593
|
+
const ownerCandidatePeerCountOf = (
|
|
3594
|
+
candidate,
|
|
3595
|
+
directTextBlockCount,
|
|
3596
|
+
descendantInteractiveCount
|
|
3597
|
+
) => {
|
|
3598
|
+
const parent = composedParentElement(candidate);
|
|
3599
|
+
if (!isHTMLElementNode(parent)) {
|
|
3600
|
+
return 0;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
const candidateShapeKey = ownerCandidateShapeKeyOf(candidate);
|
|
3604
|
+
const candidateChildCount = candidate.children.length;
|
|
3605
|
+
let peerCount = 0;
|
|
3606
|
+
|
|
3607
|
+
for (const sibling of Array.from(parent.children)) {
|
|
3608
|
+
if (!isHTMLElementNode(sibling) || sibling === candidate || !isVisible(sibling)) {
|
|
3609
|
+
continue;
|
|
3610
|
+
}
|
|
3611
|
+
if (ownerCandidateShapeKeyOf(sibling) !== candidateShapeKey) {
|
|
3612
|
+
continue;
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
const siblingInteractiveCount = visibleInteractiveDescendantCountOf(sibling);
|
|
3616
|
+
const siblingTextBlockCount = directContextTextBlockCountOf(sibling);
|
|
3617
|
+
if (Math.abs(siblingInteractiveCount - descendantInteractiveCount) > 1) {
|
|
3618
|
+
continue;
|
|
3619
|
+
}
|
|
3620
|
+
if (Math.abs(siblingTextBlockCount - directTextBlockCount) > 1) {
|
|
3621
|
+
continue;
|
|
3622
|
+
}
|
|
3623
|
+
if (Math.abs(sibling.children.length - candidateChildCount) > 1) {
|
|
3624
|
+
continue;
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
peerCount += 1;
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
return peerCount;
|
|
3631
|
+
};
|
|
3632
|
+
|
|
3633
|
+
const ownerBoundaryMetaOf = (candidate, coverage, descendantInteractiveCount) => {
|
|
3634
|
+
if (!isHTMLElementNode(candidate)) {
|
|
3635
|
+
return undefined;
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
const positionedSurface = surfacePositionTraitsOf(candidate);
|
|
3639
|
+
if (positionedSurface?.kind) {
|
|
3640
|
+
return {
|
|
3641
|
+
kind: positionedSurface.kind,
|
|
3642
|
+
stop: 'hard',
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
const role = candidate.getAttribute('role')?.trim().toLowerCase() || '';
|
|
3647
|
+
if (role === 'dialog' || candidate.getAttribute('aria-modal') === 'true') {
|
|
3648
|
+
return {
|
|
3649
|
+
kind: 'dialog',
|
|
3650
|
+
stop: 'hard',
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
if (['listbox', 'menu', 'grid', 'tabpanel'].includes(role)) {
|
|
3654
|
+
return {
|
|
3655
|
+
kind: role,
|
|
3656
|
+
stop: 'soft',
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
const tag = candidate.tagName.toLowerCase();
|
|
3661
|
+
if (tag === 'form') {
|
|
3662
|
+
return {
|
|
3663
|
+
kind: 'form',
|
|
3664
|
+
stop: 'hard',
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
const className = (candidate.getAttribute('class') || '').toLowerCase();
|
|
3669
|
+
if (className.includes('calendar') || className.includes('datepicker')) {
|
|
3670
|
+
return {
|
|
3671
|
+
kind: 'datepicker',
|
|
3672
|
+
stop: 'hard',
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
if (className.includes('popover')) {
|
|
3676
|
+
return {
|
|
3677
|
+
kind: 'popover',
|
|
3678
|
+
stop: 'hard',
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
if (className.includes('dropdown')) {
|
|
3682
|
+
return {
|
|
3683
|
+
kind: 'dropdown',
|
|
3684
|
+
stop: 'hard',
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
if (
|
|
3689
|
+
['main', 'aside', 'section'].includes(tag) &&
|
|
3690
|
+
(coverage >= 0.24 || descendantInteractiveCount > 4)
|
|
3691
|
+
) {
|
|
3692
|
+
return {
|
|
3693
|
+
kind: tag,
|
|
3694
|
+
stop: 'hard',
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
return undefined;
|
|
3699
|
+
};
|
|
3700
|
+
|
|
3701
|
+
const structuralProfileOf = (element) => {
|
|
3702
|
+
if (!isHTMLElementNode(element) || !isVisible(element)) {
|
|
3703
|
+
return undefined;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
const cachedProfile = structuralProfileCache.get(element);
|
|
3707
|
+
if (cachedProfile) {
|
|
3708
|
+
return cachedProfile;
|
|
3709
|
+
}
|
|
3710
|
+
if (cachedProfile === null) {
|
|
3711
|
+
return undefined;
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
const rect = element.getBoundingClientRect();
|
|
3715
|
+
const area = Math.max(rect.width * rect.height, 1);
|
|
3716
|
+
const coverage = area / viewportArea();
|
|
3717
|
+
const descendantInteractiveCount = visibleInteractiveDescendantCountOf(element);
|
|
3718
|
+
const descendantEditableCount = descendantEditableCountOf(element);
|
|
3719
|
+
const boundaryMeta = ownerBoundaryMetaOf(
|
|
3720
|
+
element,
|
|
3721
|
+
coverage,
|
|
3722
|
+
descendantInteractiveCount
|
|
3723
|
+
);
|
|
3724
|
+
const profile = {
|
|
3725
|
+
selector: buildSelector(element),
|
|
3726
|
+
area,
|
|
3727
|
+
coverage,
|
|
3728
|
+
descendantInteractiveCount,
|
|
3729
|
+
descendantEditableCount,
|
|
3730
|
+
directTextBlockCount: directContextTextBlockCountOf(element),
|
|
3731
|
+
boundaryMeta,
|
|
3732
|
+
explicitSurfaceKind: explicitSurfaceKindOf(element),
|
|
3733
|
+
inferredSurfaceKind: inferSurfaceKind(element),
|
|
3734
|
+
contextNode: contextNodeOf(element),
|
|
3735
|
+
surfacePriority: surfacePriorityOf(element),
|
|
3736
|
+
tag: element.tagName.toLowerCase(),
|
|
3737
|
+
role: element.getAttribute('role')?.trim().toLowerCase() || '',
|
|
3738
|
+
};
|
|
3739
|
+
structuralProfileCache.set(element, profile);
|
|
3740
|
+
incrementStructuralProfileDebugStat('structuralProfileBuilds');
|
|
3741
|
+
return profile;
|
|
3742
|
+
};
|
|
3743
|
+
|
|
3744
|
+
const ownerCandidateSurfacePriorityOf = (surfaceKind, fallbackPriority) => {
|
|
3745
|
+
if (typeof fallbackPriority === 'number' && fallbackPriority > 0) {
|
|
3746
|
+
return fallbackPriority;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
switch ((surfaceKind || '').toLowerCase()) {
|
|
3750
|
+
case 'dialog':
|
|
3751
|
+
return 100;
|
|
3752
|
+
case 'listbox':
|
|
3753
|
+
case 'menu':
|
|
3754
|
+
return 95;
|
|
3755
|
+
case 'floating-panel':
|
|
3756
|
+
return 92;
|
|
3757
|
+
case 'datepicker':
|
|
3758
|
+
return 90;
|
|
3759
|
+
case 'sticky-panel':
|
|
3760
|
+
return 88;
|
|
3761
|
+
case 'popover':
|
|
3762
|
+
case 'dropdown':
|
|
3763
|
+
return 85;
|
|
3764
|
+
case 'grid':
|
|
3765
|
+
case 'tabpanel':
|
|
3766
|
+
return 80;
|
|
3767
|
+
case 'form':
|
|
3768
|
+
return 70;
|
|
3769
|
+
case 'card':
|
|
3770
|
+
return 58;
|
|
3771
|
+
case 'group':
|
|
3772
|
+
return 55;
|
|
3773
|
+
case 'section':
|
|
3774
|
+
case 'aside':
|
|
3775
|
+
return 52;
|
|
3776
|
+
case 'listitem':
|
|
3777
|
+
case 'row':
|
|
3778
|
+
return 48;
|
|
3779
|
+
default:
|
|
3780
|
+
return undefined;
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
|
|
3784
|
+
const ownerCandidateSurfaceKindOf = (candidateProfile, metrics) => {
|
|
3785
|
+
if (!candidateProfile) {
|
|
3786
|
+
return undefined;
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
if (metrics.boundaryKind) {
|
|
3790
|
+
return metrics.boundaryKind;
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
const inferredSurfaceKind = candidateProfile.inferredSurfaceKind;
|
|
3794
|
+
if (inferredSurfaceKind && inferredSurfaceKind !== 'div') {
|
|
3795
|
+
return inferredSurfaceKind;
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
const tag = candidateProfile.tag;
|
|
3799
|
+
const role = candidateProfile.role;
|
|
3800
|
+
|
|
3801
|
+
if (tag === 'article') {
|
|
3802
|
+
return 'card';
|
|
3803
|
+
}
|
|
3804
|
+
if (tag === 'fieldset') {
|
|
3805
|
+
return 'group';
|
|
3806
|
+
}
|
|
3807
|
+
if (tag === 'li' || role === 'listitem') {
|
|
3808
|
+
return 'listitem';
|
|
3809
|
+
}
|
|
3810
|
+
if (role === 'row') {
|
|
3811
|
+
return 'row';
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
const itemLikeCard =
|
|
3815
|
+
metrics.directTextBlockCount >= 2 &&
|
|
3816
|
+
metrics.descendantInteractiveCount >= 1 &&
|
|
3817
|
+
metrics.descendantInteractiveCount <= 4 &&
|
|
3818
|
+
metrics.coverage <= 0.45 &&
|
|
3819
|
+
metrics.relativeAreaToTarget >= 2 &&
|
|
3820
|
+
(metrics.peerCount >= 1 || metrics.descendantInteractiveCount === 1);
|
|
3821
|
+
if (itemLikeCard) {
|
|
3822
|
+
return 'card';
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
return inferredSurfaceKind;
|
|
3826
|
+
};
|
|
3827
|
+
|
|
3828
|
+
const ownerCandidatesOf = (target) => {
|
|
3829
|
+
const targetRect = target.getBoundingClientRect();
|
|
3830
|
+
const targetArea = Math.max(targetRect.width * targetRect.height, 1);
|
|
3831
|
+
const candidates = [];
|
|
3832
|
+
let current = composedParentElement(target);
|
|
3833
|
+
let depth = 0;
|
|
3834
|
+
|
|
3835
|
+
while (current && depth < structuralAncestorSearchDepth) {
|
|
3836
|
+
incrementStructuralProfileDebugStat('ownerCandidateAncestorVisits');
|
|
3837
|
+
const candidateProfile = structuralProfileOf(current);
|
|
3838
|
+
if (!candidateProfile) {
|
|
3839
|
+
current = composedParentElement(current);
|
|
3840
|
+
depth += 1;
|
|
3841
|
+
continue;
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
const selectorValue = candidateProfile.selector;
|
|
3845
|
+
if (!selectorValue) {
|
|
3846
|
+
current = composedParentElement(current);
|
|
3847
|
+
depth += 1;
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3851
|
+
const candidateArea = candidateProfile.area;
|
|
3852
|
+
const coverage = candidateProfile.coverage;
|
|
3853
|
+
const descendantInteractiveCount = candidateProfile.descendantInteractiveCount;
|
|
3854
|
+
const descendantEditableCount = candidateProfile.descendantEditableCount;
|
|
3855
|
+
const directTextBlockCount = directContextTextBlockCountOf(current, target);
|
|
3856
|
+
const peerCount = ownerCandidatePeerCountOf(
|
|
3857
|
+
current,
|
|
3858
|
+
directTextBlockCount,
|
|
3859
|
+
descendantInteractiveCount
|
|
3860
|
+
);
|
|
3861
|
+
const boundaryMeta = candidateProfile.boundaryMeta;
|
|
3862
|
+
const boundaryKind = boundaryMeta?.kind;
|
|
3863
|
+
const surfaceKind = ownerCandidateSurfaceKindOf(candidateProfile, {
|
|
3864
|
+
boundaryKind,
|
|
3865
|
+
coverage,
|
|
3866
|
+
directTextBlockCount,
|
|
3867
|
+
descendantInteractiveCount,
|
|
3868
|
+
peerCount,
|
|
3869
|
+
relativeAreaToTarget: candidateArea / targetArea,
|
|
3870
|
+
});
|
|
3871
|
+
const shell =
|
|
3872
|
+
surfaceKind === 'sticky-panel' ||
|
|
3873
|
+
surfaceKind === 'floating-panel' ||
|
|
3874
|
+
(['main', 'aside', 'section'].includes(candidateProfile.tag) &&
|
|
3875
|
+
(coverage >= 0.24 || descendantInteractiveCount > 4));
|
|
3876
|
+
const boundary = Boolean(boundaryKind) || shell;
|
|
3877
|
+
const stopSearch = shell || boundaryMeta?.stop === 'hard';
|
|
3878
|
+
const contextNode = candidateProfile.contextNode;
|
|
3879
|
+
candidates.push({
|
|
3880
|
+
...contextNode,
|
|
3881
|
+
selector: selectorValue,
|
|
3882
|
+
depth,
|
|
3883
|
+
surfaceKind,
|
|
3884
|
+
surfacePriority: ownerCandidateSurfacePriorityOf(
|
|
3885
|
+
surfaceKind,
|
|
3886
|
+
candidateProfile.surfacePriority
|
|
3887
|
+
),
|
|
3888
|
+
directTextBlockCount,
|
|
3889
|
+
peerCount,
|
|
3890
|
+
descendantInteractiveCount,
|
|
3891
|
+
descendantEditableCount,
|
|
3892
|
+
viewportCoverage: coverage,
|
|
3893
|
+
relativeAreaToTarget: candidateArea / targetArea,
|
|
3894
|
+
boundary,
|
|
3895
|
+
shell,
|
|
3896
|
+
});
|
|
3897
|
+
|
|
3898
|
+
if (stopSearch) {
|
|
3899
|
+
break;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
current = composedParentElement(current);
|
|
3903
|
+
depth += 1;
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
return candidates.length > 0 ? candidates : undefined;
|
|
3907
|
+
};
|
|
3908
|
+
|
|
3261
3909
|
const targets = elements.map((element, ordinal) => {
|
|
3262
3910
|
const visualSeatGrid = visualSeatGridMeta.get(element);
|
|
3263
3911
|
const associatedChoiceControl = associatedChoiceControlOf(element);
|
|
@@ -3289,16 +3937,9 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3289
3937
|
const item =
|
|
3290
3938
|
(compactClickableAffordance ? compactClickableItemOf(element) : undefined) ||
|
|
3291
3939
|
itemOf(element);
|
|
3292
|
-
const
|
|
3293
|
-
const
|
|
3294
|
-
const
|
|
3295
|
-
genericClickable && !associatedChoiceControl && isStructuredContainer(element)
|
|
3296
|
-
? element
|
|
3297
|
-
: undefined;
|
|
3298
|
-
const surface =
|
|
3299
|
-
visualSeatGrid?.surface ||
|
|
3300
|
-
(isHTMLElementNode(overlaySurface) ? overlaySurface : localSurface || selfSurface);
|
|
3301
|
-
const surfaceSelectors = surfaceSelectorsOf(element, localSurface || selfSurface);
|
|
3940
|
+
const explicitSurface = explicitSurfaceOf(element);
|
|
3941
|
+
const surface = visualSeatGrid?.surface || explicitSurface;
|
|
3942
|
+
const surfaceSelectors = surfaceSelectorsOf(element, explicitSurface);
|
|
3302
3943
|
const structure = visualSeatGrid?.structure || inferStructuredCell(element, surface);
|
|
3303
3944
|
const directFallbackLabel = explicitLabelOf(element) || looseFieldLabelOf(element);
|
|
3304
3945
|
const directionalFallbackLabel = directionalControlFallbackLabelOf(
|
|
@@ -3309,10 +3950,23 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3309
3950
|
const currentValue = popupCurrentValueOf(element);
|
|
3310
3951
|
const selection = selectionOf(element);
|
|
3311
3952
|
const role = inferRole(element) || inferRole(associatedChoiceControl);
|
|
3312
|
-
const
|
|
3953
|
+
const elementProfile = structuralProfileOf(element);
|
|
3954
|
+
const surfaceProfile = surface ? structuralProfileOf(surface) : undefined;
|
|
3955
|
+
const explicitSurfaceProfile = explicitSurface
|
|
3956
|
+
? structuralProfileOf(explicitSurface)
|
|
3957
|
+
: undefined;
|
|
3958
|
+
const surfaceKind =
|
|
3959
|
+
visualSeatGrid?.surfaceKind ||
|
|
3960
|
+
surfaceProfile?.inferredSurfaceKind ||
|
|
3961
|
+
surfaceKindOf(surface);
|
|
3313
3962
|
const fallbackSurfaceLabel =
|
|
3314
3963
|
(visualSeatGrid?.hintText ? 'Seat map' : undefined) ||
|
|
3315
3964
|
surfaceFallbackLabelOf(surface, surfaceKind);
|
|
3965
|
+
const explicitSurfaceKind = explicitSurfaceProfile?.explicitSurfaceKind;
|
|
3966
|
+
const explicitFallbackSurfaceLabel =
|
|
3967
|
+
explicitSurface && explicitSurfaceKind
|
|
3968
|
+
? surfaceFallbackLabelOf(explicitSurface, explicitSurfaceKind)
|
|
3969
|
+
: undefined;
|
|
3316
3970
|
const form = composedClosest(element, 'form');
|
|
3317
3971
|
const testIdAttribute = element.hasAttribute('data-testid')
|
|
3318
3972
|
? 'data-testid'
|
|
@@ -3362,17 +4016,28 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3362
4016
|
surfaceKind,
|
|
3363
4017
|
surfaceLabel: fallbackSurfaceLabel,
|
|
3364
4018
|
fallbackSurfaceLabel,
|
|
3365
|
-
surfaceSelector:
|
|
4019
|
+
surfaceSelector: surfaceProfile?.selector,
|
|
3366
4020
|
surfaceSelectors,
|
|
3367
|
-
surfacePriority: surfacePriorityOf(surface),
|
|
4021
|
+
surfacePriority: surfaceProfile?.surfacePriority ?? surfacePriorityOf(surface),
|
|
4022
|
+
explicitSurfaceKind,
|
|
4023
|
+
explicitSurfaceLabel: explicitFallbackSurfaceLabel,
|
|
4024
|
+
explicitFallbackSurfaceLabel,
|
|
4025
|
+
explicitSurfaceSelector: explicitSurfaceProfile?.selector,
|
|
4026
|
+
explicitSurfaceSelectors: surfaceSelectors,
|
|
4027
|
+
explicitSurfacePriority:
|
|
4028
|
+
explicitSurfaceProfile?.surfacePriority ??
|
|
4029
|
+
(explicitSurface ? surfacePriorityOf(explicitSurface) : undefined),
|
|
3368
4030
|
controlsSurfaceSelector:
|
|
3369
4031
|
selectorFromRelation(element, 'aria-controls') || selectorFromRelation(element, 'aria-owns'),
|
|
3370
4032
|
formSelector: form ? buildSelector(form) : undefined,
|
|
3371
4033
|
pageFormSelector: form ? buildSelector(form) : inheritedPageFormSelector || undefined,
|
|
3372
|
-
descendantInteractiveCount:
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
4034
|
+
descendantInteractiveCount:
|
|
4035
|
+
elementProfile?.descendantInteractiveCount ?? element.querySelectorAll(selector).length,
|
|
4036
|
+
descendantEditableCount:
|
|
4037
|
+
elementProfile?.descendantEditableCount ??
|
|
4038
|
+
element.querySelectorAll(
|
|
4039
|
+
'input:not([type="hidden"]), textarea, select, [contenteditable="true"]'
|
|
4040
|
+
).length,
|
|
3376
4041
|
structure,
|
|
3377
4042
|
ordinal,
|
|
3378
4043
|
context: {
|
|
@@ -3387,11 +4052,15 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3387
4052
|
hintText: describedByTextOf(element),
|
|
3388
4053
|
fallbackHintText: visualSeatGrid?.hintText,
|
|
3389
4054
|
visual: visualOf(element),
|
|
4055
|
+
ownerCandidates: ownerCandidatesOf(element),
|
|
3390
4056
|
},
|
|
3391
4057
|
};
|
|
3392
4058
|
});
|
|
3393
4059
|
|
|
3394
|
-
return
|
|
4060
|
+
return {
|
|
4061
|
+
targets,
|
|
4062
|
+
debugStats: structuralProfileDebugStats,
|
|
4063
|
+
};
|
|
3395
4064
|
};
|
|
3396
4065
|
|
|
3397
4066
|
const enrichDisplayLabels = (targets) => {
|
|
@@ -3494,8 +4163,9 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3494
4163
|
return score;
|
|
3495
4164
|
};
|
|
3496
4165
|
|
|
4166
|
+
const collectionResult = collectTargets(document);
|
|
3497
4167
|
const seen = new Set();
|
|
3498
|
-
|
|
4168
|
+
const finalTargets = enrichDisplayLabels(collectionResult.targets)
|
|
3499
4169
|
.filter((candidate) => {
|
|
3500
4170
|
const frameKey = candidate.framePath ? candidate.framePath.join(' -> ') : 'top';
|
|
3501
4171
|
const key = frameKey + '|' + (candidate.selector || candidate.domSignature || '');
|
|
@@ -3524,18 +4194,55 @@ async function collectDomTargetsFromDocument(context, options) {
|
|
|
3524
4194
|
return left.index - right.index;
|
|
3525
4195
|
})
|
|
3526
4196
|
.map(({ candidate }) => candidate);
|
|
4197
|
+
|
|
4198
|
+
return debugStructuralProfileStats
|
|
4199
|
+
? {
|
|
4200
|
+
targets: finalTargets,
|
|
4201
|
+
debugStats: collectionResult.debugStats,
|
|
4202
|
+
}
|
|
4203
|
+
: finalTargets;
|
|
3527
4204
|
})()`);
|
|
3528
|
-
|
|
3529
|
-
|
|
4205
|
+
let observedTargets = [];
|
|
4206
|
+
let debugStats;
|
|
4207
|
+
if (Array.isArray(observedPayload)) {
|
|
4208
|
+
observedTargets = observedPayload;
|
|
4209
|
+
}
|
|
4210
|
+
else if (observedPayload &&
|
|
4211
|
+
typeof observedPayload === 'object' &&
|
|
4212
|
+
Array.isArray(observedPayload.targets)) {
|
|
4213
|
+
observedTargets = observedPayload.targets;
|
|
4214
|
+
const candidateDebugStats = observedPayload.debugStats;
|
|
4215
|
+
if (candidateDebugStats &&
|
|
4216
|
+
typeof candidateDebugStats === 'object' &&
|
|
4217
|
+
typeof candidateDebugStats
|
|
4218
|
+
.structuralProfileBuilds === 'number' &&
|
|
4219
|
+
typeof candidateDebugStats
|
|
4220
|
+
.ownerCandidateAncestorVisits === 'number' &&
|
|
4221
|
+
typeof candidateDebugStats
|
|
4222
|
+
.explicitSurfaceAncestorVisits === 'number' &&
|
|
4223
|
+
typeof candidateDebugStats
|
|
4224
|
+
.surfaceSelectorAncestorVisits === 'number') {
|
|
4225
|
+
debugStats = candidateDebugStats;
|
|
4226
|
+
}
|
|
3530
4227
|
}
|
|
3531
|
-
|
|
3532
|
-
.filter((target) => Boolean(target &&
|
|
4228
|
+
const targets = observedTargets
|
|
4229
|
+
.filter((target) => Boolean(target &&
|
|
4230
|
+
typeof target === 'object' &&
|
|
4231
|
+
typeof target.kind === 'string'))
|
|
3533
4232
|
.map((target) => enrichObservedTargetSemantics(applyInheritedDomTargetMetadata(target, {
|
|
3534
4233
|
framePath: options?.framePath,
|
|
3535
4234
|
frameUrl: options?.frameUrl,
|
|
3536
4235
|
pageSignature: options?.pageSignature,
|
|
3537
4236
|
pageFormSelector: options?.pageFormSelector,
|
|
3538
4237
|
})));
|
|
4238
|
+
return {
|
|
4239
|
+
targets,
|
|
4240
|
+
debugStats,
|
|
4241
|
+
};
|
|
4242
|
+
}
|
|
4243
|
+
async function collectDomTargetsFromDocument(context, options) {
|
|
4244
|
+
const { targets } = await collectDomTargetsFromDocumentRaw(context, options);
|
|
4245
|
+
return targets;
|
|
3539
4246
|
}
|
|
3540
4247
|
const FRAME_HOST_DESCRIPTOR_SCRIPT = String.raw `
|
|
3541
4248
|
const ownerWindowOf = (node) => node?.ownerDocument?.defaultView || window;
|
|
@@ -3856,6 +4563,10 @@ export async function collectDomTargets(page, options) {
|
|
|
3856
4563
|
}
|
|
3857
4564
|
export const __testDomTargetCollection = {
|
|
3858
4565
|
collectDomTargetsFromDocument,
|
|
4566
|
+
collectDomTargetsDebugFromDocument: (context, options) => collectDomTargetsFromDocumentRaw(context, {
|
|
4567
|
+
...options,
|
|
4568
|
+
debugStructuralProfileStats: true,
|
|
4569
|
+
}),
|
|
3859
4570
|
inferStructuredCellVariantFromEvidence,
|
|
3860
4571
|
inferDirectionalControlFallbackFromEvidence,
|
|
3861
4572
|
locatorDomSignatureScript: LOCATOR_DOM_SIGNATURE_SCRIPT,
|