browser-debugger-cli 0.8.0 → 0.9.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.
- package/README.md +4 -1
- package/dist/cdp/schema.d.ts +4 -1
- package/dist/cdp/schema.js +48 -7
- package/dist/commands/cdp.js +3 -2
- package/dist/commands/cleanup.d.ts +11 -0
- package/dist/commands/cleanup.js +161 -57
- package/dist/commands/console.d.ts +20 -1
- package/dist/commands/console.js +57 -17
- package/dist/commands/details.js +3 -2
- package/dist/commands/dom/DomElementResolver.d.ts +10 -3
- package/dist/commands/dom/DomElementResolver.js +35 -17
- package/dist/commands/dom/a11y.d.ts +10 -0
- package/dist/commands/dom/a11y.js +27 -5
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +29 -4
- package/dist/commands/dom/form.js +16 -62
- package/dist/commands/dom/formInteraction.js +152 -113
- package/dist/commands/dom/formSummary.d.ts +49 -0
- package/dist/commands/dom/formSummary.js +180 -0
- package/dist/commands/dom/frames.d.ts +2 -1
- package/dist/commands/dom/frames.js +17 -2
- package/dist/commands/dom/get.d.ts +6 -5
- package/dist/commands/dom/get.js +92 -82
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/query.d.ts +44 -17
- package/dist/commands/dom/helpers/query.js +244 -97
- package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
- package/dist/commands/dom/helpers/runElementCommand.js +97 -30
- package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
- package/dist/commands/dom/helpers/screenshot.js +164 -49
- package/dist/commands/dom/index.d.ts +3 -1
- package/dist/commands/dom/index.js +16 -6
- package/dist/commands/dom/layout.d.ts +14 -0
- package/dist/commands/dom/layout.js +54 -0
- package/dist/commands/dom/listeners.d.ts +5 -1
- package/dist/commands/dom/listeners.js +13 -3
- package/dist/commands/dom/query.js +2 -3
- package/dist/commands/dom/screenshot.d.ts +12 -2
- package/dist/commands/dom/screenshot.js +27 -3
- package/dist/commands/dom/semanticUtils.d.ts +6 -13
- package/dist/commands/dom/semanticUtils.js +15 -19
- package/dist/commands/dom/wait.d.ts +13 -0
- package/dist/commands/dom/wait.js +83 -0
- package/dist/commands/helpJson.js +2 -2
- package/dist/commands/network/list.js +4 -11
- package/dist/commands/optionBehaviors.js +112 -21
- package/dist/commands/page.d.ts +2 -1
- package/dist/commands/page.js +41 -5
- package/dist/commands/peek.js +4 -11
- package/dist/commands/sessions.d.ts +8 -0
- package/dist/commands/sessions.js +19 -0
- package/dist/commands/shared/CommandRunner.js +4 -4
- package/dist/commands/shared/dataFetcher.js +2 -2
- package/dist/commands/shared/followMode.d.ts +21 -1
- package/dist/commands/shared/followMode.js +29 -2
- package/dist/commands/shared/handleValidationError.d.ts +2 -2
- package/dist/commands/shared/handleValidationError.js +12 -3
- package/dist/commands/shared/optionTypes.d.ts +40 -5
- package/dist/commands/shared/startHelpers.js +12 -3
- package/dist/commands/shared/validation.d.ts +3 -2
- package/dist/commands/shared/validation.js +4 -3
- package/dist/commands/start.d.ts +63 -0
- package/dist/commands/start.js +115 -15
- package/dist/commands/status.js +29 -7
- package/dist/commands/stop.js +7 -6
- package/dist/commands/tail.js +4 -11
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands.js +2 -0
- package/dist/connection/chromeIdentity.d.ts +65 -0
- package/dist/connection/chromeIdentity.js +143 -0
- package/dist/connection/launcher/profilePreferences.d.ts +47 -0
- package/dist/connection/launcher/profilePreferences.js +151 -0
- package/dist/connection/launcher.d.ts +21 -2
- package/dist/connection/launcher.js +42 -16
- package/dist/connection/portReservation.d.ts +14 -4
- package/dist/connection/portReservation.js +21 -6
- package/dist/connection/startupExit.d.ts +8 -0
- package/dist/connection/startupExit.js +15 -6
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +9 -2
- package/dist/daemon/SessionController.js +23 -7
- package/dist/daemon/errors.d.ts +1 -1
- package/dist/daemon/errors.js +1 -1
- package/dist/daemon/launcher.d.ts +2 -1
- package/dist/daemon/launcher.js +5 -6
- package/dist/daemon/server/SocketServer.js +1 -2
- package/dist/daemon/session/Session.d.ts +13 -0
- package/dist/daemon/session/Session.js +57 -8
- package/dist/daemon/session/chromeConnection.d.ts +9 -0
- package/dist/daemon/session/chromeConnection.js +45 -8
- package/dist/daemon/session/commandRegistry.js +52 -62
- package/dist/daemon/session/interactions.d.ts +35 -9
- package/dist/daemon/session/interactions.js +36 -9
- package/dist/daemon/session/triggeredRequests.d.ts +67 -0
- package/dist/daemon/session/triggeredRequests.js +157 -0
- package/dist/daemon/session/types.d.ts +5 -1
- package/dist/daemon.js +5393 -1600
- package/dist/errors/messages.d.ts +387 -24
- package/dist/errors/messages.js +761 -67
- package/dist/index.js +3976 -1558
- package/dist/ipc/client.d.ts +12 -1
- package/dist/ipc/client.js +22 -3
- package/dist/ipc/protocol/commands.d.ts +89 -4
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +258 -7
- package/dist/ipc/session/lifecycle.d.ts +8 -1
- package/dist/ipc/session/queries.d.ts +5 -1
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/index.d.ts +2 -1
- package/dist/ipc/transport/index.js +2 -2
- package/dist/runtime/dom/actionEffects.d.ts +106 -0
- package/dist/runtime/dom/actionEffects.js +256 -0
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
- package/dist/runtime/dom/actionEffectsScripts.js +234 -0
- package/dist/runtime/dom/elementGeometry.d.ts +170 -0
- package/dist/runtime/dom/elementGeometry.js +553 -0
- package/dist/runtime/dom/elementInfo.d.ts +77 -0
- package/dist/runtime/dom/elementInfo.js +191 -0
- package/dist/runtime/dom/evalHelpers.d.ts +51 -6
- package/dist/runtime/dom/evalHelpers.js +136 -26
- package/dist/runtime/dom/eventListeners.d.ts +2 -1
- package/dist/runtime/dom/eventListeners.js +174 -47
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/formDiscovery.js +116 -16
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
- package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
- package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
- package/dist/runtime/dom/formFillHelpers/index.js +2 -2
- package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
- package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
- package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
- package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
- package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
- package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
- package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
- package/dist/runtime/dom/formSubmitHelpers.js +89 -15
- package/dist/runtime/dom/frameLayout.d.ts +60 -0
- package/dist/runtime/dom/frameLayout.js +140 -0
- package/dist/runtime/dom/frameOrigin.d.ts +50 -0
- package/dist/runtime/dom/frameOrigin.js +62 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
- package/dist/runtime/dom/frameScopedConnection.js +252 -0
- package/dist/runtime/dom/frameSelection.d.ts +1 -1
- package/dist/runtime/dom/frameSelection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +25 -2
- package/dist/runtime/dom/frames.js +202 -63
- package/dist/runtime/dom/layout.d.ts +67 -0
- package/dist/runtime/dom/layout.js +333 -0
- package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
- package/dist/runtime/dom/listenerPageScripts.js +279 -0
- package/dist/runtime/dom/listenerSummary.d.ts +132 -11
- package/dist/runtime/dom/listenerSummary.js +344 -22
- package/dist/runtime/dom/pageActivity.d.ts +41 -0
- package/dist/runtime/dom/pageActivity.js +123 -0
- package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
- package/dist/runtime/dom/reactEventHelpers.js +212 -41
- package/dist/runtime/dom/targetNode.d.ts +80 -27
- package/dist/runtime/dom/targetNode.js +249 -33
- package/dist/runtime/dom/wait.d.ts +25 -0
- package/dist/runtime/dom/wait.js +199 -0
- package/dist/runtime/dom/waitCondition.d.ts +71 -0
- package/dist/runtime/dom/waitCondition.js +75 -0
- package/dist/runtime/page/emulation.d.ts +51 -0
- package/dist/runtime/page/emulation.js +80 -0
- package/dist/runtime/page/loadingState.d.ts +36 -0
- package/dist/runtime/page/loadingState.js +86 -0
- package/dist/runtime/page/navigation.d.ts +46 -2
- package/dist/runtime/page/navigation.js +69 -33
- package/dist/session/QueryCacheManager.d.ts +11 -1
- package/dist/session/QueryCacheManager.js +25 -3
- package/dist/session/chromeOwners.d.ts +34 -0
- package/dist/session/chromeOwners.js +51 -0
- package/dist/session/cleanup/staleSession.d.ts +11 -1
- package/dist/session/cleanup/staleSession.js +17 -6
- package/dist/session/cleanup/userCommands.js +2 -4
- package/dist/session/metadata.d.ts +5 -1
- package/dist/session/metadata.js +2 -1
- package/dist/session/paths.d.ts +77 -3
- package/dist/session/paths.js +111 -5
- package/dist/session/port.d.ts +31 -7
- package/dist/session/port.js +50 -43
- package/dist/session/portClaims.d.ts +66 -0
- package/dist/session/portClaims.js +284 -0
- package/dist/session/sessionList.d.ts +58 -0
- package/dist/session/sessionList.js +199 -0
- package/dist/session/sessionName.d.ts +46 -0
- package/dist/session/sessionName.js +97 -0
- package/dist/telemetry/a11y.d.ts +8 -3
- package/dist/telemetry/a11y.js +92 -28
- package/dist/telemetry/requestKinds.d.ts +32 -0
- package/dist/telemetry/requestKinds.js +61 -0
- package/dist/telemetry/requestState.d.ts +31 -0
- package/dist/telemetry/requestState.js +38 -0
- package/dist/types.d.ts +80 -3
- package/dist/ui/formatters/a11y.js +3 -0
- package/dist/ui/formatters/console/chronological.d.ts +8 -0
- package/dist/ui/formatters/console/chronological.js +17 -4
- package/dist/ui/formatters/console/json.js +3 -4
- package/dist/ui/formatters/console/shared.d.ts +12 -0
- package/dist/ui/formatters/console.d.ts +2 -2
- package/dist/ui/formatters/console.js +1 -1
- package/dist/ui/formatters/details.js +2 -1
- package/dist/ui/formatters/dom.d.ts +26 -14
- package/dist/ui/formatters/dom.js +65 -52
- package/dist/ui/formatters/form.js +29 -18
- package/dist/ui/formatters/layout.d.ts +31 -0
- package/dist/ui/formatters/layout.js +53 -0
- package/dist/ui/formatters/listeners.d.ts +3 -2
- package/dist/ui/formatters/listeners.js +73 -9
- package/dist/ui/formatters/networkHeaders.js +13 -0
- package/dist/ui/formatters/preview.js +2 -1
- package/dist/ui/formatters/requestStatus.d.ts +1 -17
- package/dist/ui/formatters/requestStatus.js +2 -30
- package/dist/ui/formatters/sessions.d.ts +12 -0
- package/dist/ui/formatters/sessions.js +40 -0
- package/dist/ui/formatters/status.d.ts +21 -2
- package/dist/ui/formatters/status.js +47 -10
- package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
- package/dist/ui/formatters/triggeredRequests.js +65 -0
- package/dist/ui/formatting.d.ts +10 -0
- package/dist/ui/formatting.js +28 -36
- package/dist/ui/messages/chrome.d.ts +9 -0
- package/dist/ui/messages/chrome.js +17 -5
- package/dist/ui/messages/commands.d.ts +388 -14
- package/dist/ui/messages/commands.js +664 -21
- package/dist/ui/messages/consoleMessages.d.ts +10 -0
- package/dist/ui/messages/consoleMessages.js +17 -0
- package/dist/ui/messages/hints.js +2 -1
- package/dist/ui/messages/preview.js +5 -4
- package/dist/ui/messages/session.d.ts +16 -21
- package/dist/ui/messages/session.js +28 -26
- package/dist/ui/messages/sessionCommand.d.ts +43 -0
- package/dist/ui/messages/sessionCommand.js +52 -0
- package/dist/utils/async.d.ts +8 -0
- package/dist/utils/async.js +19 -0
- package/dist/utils/http.d.ts +22 -1
- package/dist/utils/http.js +28 -9
- package/dist/utils/selectorFilters.d.ts +36 -8
- package/dist/utils/selectorFilters.js +267 -53
- package/dist/utils/shellDetection.d.ts +8 -2
- package/dist/utils/shellDetection.js +120 -33
- package/dist/utils/suggestions.d.ts +26 -0
- package/dist/utils/suggestions.js +73 -0
- package/dist/utils/taskMappings.js +10 -0
- package/dist/utils/url.d.ts +12 -2
- package/dist/utils/url.js +69 -7
- package/package.json +1 -1
- package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
- package/dist/ui/formatters/sessionFormatters.js +0 -121
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Positions, sizes and visibility of elements (`bdg dom layout`).
|
|
3
|
+
*
|
|
4
|
+
* One page-side pass per command measures every match: its box in page and
|
|
5
|
+
* viewport coordinates (iframe offsets included), what clips it, whether it
|
|
6
|
+
* is rendered, what covers its center and the styles that decide how it
|
|
7
|
+
* shows. The measurements are classified outside the page
|
|
8
|
+
* ({@link classifyViewportPosition}).
|
|
9
|
+
*/
|
|
10
|
+
import { CommandError } from '../../errors/index.js';
|
|
11
|
+
import { operationFailedError } from '../../errors/messages.js';
|
|
12
|
+
import { ELEMENT_GEOMETRY_JS, FRAME_OFFSET_JS, VIEWPORT_SIZE_JS, classifyViewportPosition, } from './elementGeometry.js';
|
|
13
|
+
import { ELEMENT_CONTEXT_JS, ELEMENT_DESCRIPTION_JS, ELEMENT_TEXT_JS, textPreview, } from './elementInfo.js';
|
|
14
|
+
import { throwIfInvalidSelector } from './formFillHelpers/shared.js';
|
|
15
|
+
import { findFrameOwner, placeInOwnerFrame } from './frameLayout.js';
|
|
16
|
+
import { measureFrameMapping } from './frameScopedConnection.js';
|
|
17
|
+
import { DEEP_QUERY_JS, missingElementError, selectorArgsJS } from './targetNode.js';
|
|
18
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
19
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
20
|
+
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
21
|
+
const log = createLogger('dom');
|
|
22
|
+
/** Matches measured per command (the rest are counted as omitted) */
|
|
23
|
+
export const LAYOUT_ELEMENT_LIMIT = 100;
|
|
24
|
+
/**
|
|
25
|
+
* Page-side end of a visible span kept clear of an overlay scrollbar along
|
|
26
|
+
* an edge (16 CSS px wide): overlay scrollbars (macOS, mobile) show for about a
|
|
27
|
+
* second after a scroll and catch hit tests, which then return the scroller.
|
|
28
|
+
* The span ends before the strip when it reaches into it and also shows
|
|
29
|
+
* outside it; without overlay scrollbars along the edge it is unchanged.
|
|
30
|
+
* Arguments: span start and end, the edge, whether overlay scrollbars show there.
|
|
31
|
+
*/
|
|
32
|
+
export const CLEAR_OF_SCROLLBAR_JS = `(start, end, edge, overlay) =>
|
|
33
|
+
overlay && end > edge - 16 && edge - 16 > start ? edge - 16 : end`;
|
|
34
|
+
/**
|
|
35
|
+
* Page-side edges of the top-level viewport with overlay scrollbars: the page
|
|
36
|
+
* scrolls along an axis while its scrollbar takes no space (the window is as
|
|
37
|
+
* wide or tall as the viewport).
|
|
38
|
+
*/
|
|
39
|
+
const VIEWPORT_OVERLAY_JS = `(view, viewport) => {
|
|
40
|
+
const scroller = view.document.scrollingElement || view.document.documentElement;
|
|
41
|
+
return {
|
|
42
|
+
right: scroller.scrollHeight > viewport.height && view.innerWidth - viewport.width < 1,
|
|
43
|
+
bottom: scroller.scrollWidth > viewport.width && view.innerHeight - viewport.height < 1
|
|
44
|
+
};
|
|
45
|
+
}`;
|
|
46
|
+
/** Elements a click on any of their content activates */
|
|
47
|
+
const CLICK_TARGET_SELECTOR = 'a[href], button, [role="button"], [role="link"], label, summary, input, select, textarea';
|
|
48
|
+
/**
|
|
49
|
+
* Page-side test of whether an element on top of another one is part of the
|
|
50
|
+
* same click target, so a click there does what a click on the element
|
|
51
|
+
* would: both are in the same link, button or label (an overlay span inside
|
|
52
|
+
* the card's link), or in links to the same URL (a card's overlay link next
|
|
53
|
+
* to its headline link), or the element is plain content (in no link or
|
|
54
|
+
* button) of a card covered by its "faux block link" (news sites): an
|
|
55
|
+
* absolutely positioned `<a href>` without visible text of its own (empty,
|
|
56
|
+
* or an aria-label only), directly in the card (not `<body>`) and spanning
|
|
57
|
+
* it. Dismiss buttons, promo links with text and `[role=button]` overlays
|
|
58
|
+
* still count as covers.
|
|
59
|
+
*/
|
|
60
|
+
const SAME_CLICK_TARGET_JS = `(node, hit) => {
|
|
61
|
+
const parentOf = (n) => n.parentElement || (n.parentNode && n.parentNode.host) || null;
|
|
62
|
+
const targetOf = (n) => {
|
|
63
|
+
for (let p = n; p; p = parentOf(p)) {
|
|
64
|
+
if (p.matches && p.matches(${JSON.stringify(CLICK_TARGET_SELECTOR)})) return p;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
const theirs = targetOf(hit);
|
|
69
|
+
if (!theirs) return false;
|
|
70
|
+
const mine = targetOf(node);
|
|
71
|
+
if (mine) return mine === theirs || (mine.localName === 'a' && theirs.localName === 'a' && mine.href === theirs.href);
|
|
72
|
+
const card = theirs.parentElement;
|
|
73
|
+
if (theirs.localName !== 'a' || theirs.hasAttribute('role')) return false;
|
|
74
|
+
if (!card || card === node.ownerDocument.body || !card.contains(node)) return false;
|
|
75
|
+
const style = theirs.ownerDocument.defaultView.getComputedStyle(theirs);
|
|
76
|
+
if (style.position !== 'absolute' || (theirs.innerText || '').trim() !== '') return false;
|
|
77
|
+
const outer = theirs.getBoundingClientRect();
|
|
78
|
+
const inner = card.getBoundingClientRect();
|
|
79
|
+
return outer.left <= inner.left + 1 && outer.top <= inner.top + 1 && outer.right >= inner.right - 1 && outer.bottom >= inner.bottom - 1;
|
|
80
|
+
}`;
|
|
81
|
+
/**
|
|
82
|
+
* Page function: layout of the matches in `found` (all up to `limit`, or the
|
|
83
|
+
* one at `index`) and of the top-level page. An element covers another when
|
|
84
|
+
* it is the topmost element at the center of the largest visible part of the
|
|
85
|
+
* other's boxes (a wrapped link has one per line; the strip where overlay
|
|
86
|
+
* scrollbars show is avoided when possible, {@link CLEAR_OF_SCROLLBAR_JS}) and does not lie inside it,
|
|
87
|
+
* nor part of the element's own click target ({@link SAME_CLICK_TARGET_JS}: the link, button
|
|
88
|
+
* or label it is in, or the overlay link of its card; a click there does the same). An
|
|
89
|
+
* ancestor covers it only when the ancestor is painted above it there (its
|
|
90
|
+
* `::before`/`::after` overlay, or its background over a negative
|
|
91
|
+
* `z-index`), as `dom click` finds: the element is in the hit-test stack below
|
|
92
|
+
* the ancestor. Hit-testing goes up through the iframes, so an overlay over
|
|
93
|
+
* an iframe covers the elements in it. Elements hit-testing skips
|
|
94
|
+
* (`pointer-events: none`, also through an iframe) get no cover.
|
|
95
|
+
*/
|
|
96
|
+
const LAYOUT_JS = `function (found, index, limit) {
|
|
97
|
+
const geometryOf = ${ELEMENT_GEOMETRY_JS};
|
|
98
|
+
const viewportSize = ${VIEWPORT_SIZE_JS};
|
|
99
|
+
const frameOffset = ${FRAME_OFFSET_JS};
|
|
100
|
+
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
101
|
+
const contextOf = ${ELEMENT_CONTEXT_JS};
|
|
102
|
+
const textOf = ${ELEMENT_TEXT_JS};
|
|
103
|
+
const clearOfScrollbar = ${CLEAR_OF_SCROLLBAR_JS};
|
|
104
|
+
const viewportOverlay = ${VIEWPORT_OVERLAY_JS};
|
|
105
|
+
const picked = index === null
|
|
106
|
+
? found.slice(0, limit).map((el, i) => [i, el])
|
|
107
|
+
: (found[index] ? [[index, found[index]]] : []);
|
|
108
|
+
if (picked.length === 0) return { count: found.length, elements: [] };
|
|
109
|
+
let top = picked[0][1].ownerDocument.defaultView;
|
|
110
|
+
while (top.frameElement) top = top.parent;
|
|
111
|
+
const scroller = top.document.scrollingElement || top.document.documentElement;
|
|
112
|
+
const page = {
|
|
113
|
+
viewport: viewportSize(top),
|
|
114
|
+
scroll: { x: top.scrollX, y: top.scrollY },
|
|
115
|
+
document: { width: scroller.scrollWidth, height: scroller.scrollHeight },
|
|
116
|
+
colorScheme: top.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
117
|
+
};
|
|
118
|
+
const pageOverlay = viewportOverlay(top, page.viewport);
|
|
119
|
+
const encloses = (outer, node) => {
|
|
120
|
+
for (let n = node; n; n = n.parentNode || n.host) if (n === outer) return true;
|
|
121
|
+
return false;
|
|
122
|
+
};
|
|
123
|
+
const sameTarget = ${SAME_CLICK_TARGET_JS};
|
|
124
|
+
const coverAt = (node, x, y) => {
|
|
125
|
+
const root = node.getRootNode();
|
|
126
|
+
const scope = typeof root.elementFromPoint === 'function' ? root : node.ownerDocument;
|
|
127
|
+
const hit = scope.elementFromPoint(x, y);
|
|
128
|
+
if (!hit || encloses(node, hit) || sameTarget(node, hit)) return null;
|
|
129
|
+
if (!encloses(hit, node)) return hit;
|
|
130
|
+
return scope.elementsFromPoint(x, y).indexOf(node) > 0 ? hit : null;
|
|
131
|
+
};
|
|
132
|
+
const visibleCenter = (el, g) => {
|
|
133
|
+
const bounds = [{ x: 0, y: 0, width: page.viewport.width, height: page.viewport.height }].concat(g.clip ? [g.clip] : []);
|
|
134
|
+
let best = null;
|
|
135
|
+
for (const r of Array.from(el.getClientRects())) {
|
|
136
|
+
const boxes = bounds.concat([{ x: r.left + g.offset.x, y: r.top + g.offset.y, width: r.width, height: r.height }]);
|
|
137
|
+
const left = Math.max(...boxes.map((b) => b.x));
|
|
138
|
+
const top = Math.max(...boxes.map((b) => b.y));
|
|
139
|
+
const right = Math.min(...boxes.map((b) => b.x + b.width));
|
|
140
|
+
const bottom = Math.min(...boxes.map((b) => b.y + b.height));
|
|
141
|
+
const area = (right - left) * (bottom - top);
|
|
142
|
+
if (!(right > left && bottom > top) || (best && area <= best.area)) continue;
|
|
143
|
+
let clearRight = clearOfScrollbar(left, right, page.viewport.width, pageOverlay.right);
|
|
144
|
+
let clearBottom = clearOfScrollbar(top, bottom, page.viewport.height, pageOverlay.bottom);
|
|
145
|
+
if (g.clip) {
|
|
146
|
+
clearRight = clearOfScrollbar(left, clearRight, g.clip.x + g.clip.width, g.clipOverlay.right);
|
|
147
|
+
clearBottom = clearOfScrollbar(top, clearBottom, g.clip.y + g.clip.height, g.clipOverlay.bottom);
|
|
148
|
+
}
|
|
149
|
+
best = { area: area, x: (left + clearRight) / 2, y: (top + clearBottom) / 2 };
|
|
150
|
+
}
|
|
151
|
+
return best;
|
|
152
|
+
};
|
|
153
|
+
const hitTestable = (node) => node.ownerDocument.defaultView.getComputedStyle(node).pointerEvents !== 'none';
|
|
154
|
+
const coveredBy = (el, g) => {
|
|
155
|
+
const center = hitTestable(el) && visibleCenter(el, g);
|
|
156
|
+
if (!center) return null;
|
|
157
|
+
let x = center.x - g.offset.x;
|
|
158
|
+
let y = center.y - g.offset.y;
|
|
159
|
+
let cover = coverAt(el, x, y);
|
|
160
|
+
for (let view = el.ownerDocument.defaultView; !cover && view.frameElement; view = view.parent) {
|
|
161
|
+
if (!hitTestable(view.frameElement)) return null;
|
|
162
|
+
const offset = frameOffset(view.frameElement);
|
|
163
|
+
x += offset.x;
|
|
164
|
+
y += offset.y;
|
|
165
|
+
cover = coverAt(view.frameElement, x, y);
|
|
166
|
+
}
|
|
167
|
+
return cover ? describe(cover) : null;
|
|
168
|
+
};
|
|
169
|
+
const elements = picked.map(([i, el]) => {
|
|
170
|
+
const style = el.ownerDocument.defaultView.getComputedStyle(el);
|
|
171
|
+
const geometry = geometryOf(el);
|
|
172
|
+
return {
|
|
173
|
+
index: i,
|
|
174
|
+
tag: el.tagName.toLowerCase(),
|
|
175
|
+
element: describe(el),
|
|
176
|
+
text: String(textOf(el)).replace(/\\s+/g, ' ').trim().slice(0, 1000),
|
|
177
|
+
context: contextOf(el),
|
|
178
|
+
geometry: geometry,
|
|
179
|
+
coveredBy: geometry.hidden ? null : coveredBy(el, geometry),
|
|
180
|
+
computed: { display: style.display, visibility: style.visibility, position: style.position, opacity: style.opacity, zIndex: style.zIndex }
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
return { count: found.length, page: page, elements: elements, crossOriginFrame: top.parent !== top };
|
|
184
|
+
}`;
|
|
185
|
+
/** Distinguishes the object groups of concurrent calls */
|
|
186
|
+
let groupCounter = 0;
|
|
187
|
+
/**
|
|
188
|
+
* Measure the elements a selector (or cached index) refers to.
|
|
189
|
+
*
|
|
190
|
+
* @param cdp - CDP connection
|
|
191
|
+
* @param params - Selector (and index) or backend node id
|
|
192
|
+
* @returns Layout report
|
|
193
|
+
* @throws CommandError (83) no match, (81) index out of range or invalid
|
|
194
|
+
* selector, (87) the cached element left the page
|
|
195
|
+
*/
|
|
196
|
+
export async function inspectLayout(cdp, params) {
|
|
197
|
+
const objectGroup = `bdg-layout-${++groupCounter}`;
|
|
198
|
+
try {
|
|
199
|
+
const raw = params.backendNodeId === undefined
|
|
200
|
+
? await measureSelector(cdp, params)
|
|
201
|
+
: await measureNode(cdp, params.backendNodeId, objectGroup);
|
|
202
|
+
if (!raw.page || raw.elements.length === 0)
|
|
203
|
+
throw missingElementError(params, raw.count);
|
|
204
|
+
return buildLayoutResult({ ...raw, page: raw.page }, params);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
void cdp
|
|
208
|
+
.send('Runtime.releaseObjectGroup', { objectGroup })
|
|
209
|
+
.catch((error) => log.debug(`Object group not released: ${getErrorMessage(error)}`));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Measure the matches of a selector (searching open shadow roots and
|
|
214
|
+
* same-origin frames) in one page evaluation.
|
|
215
|
+
*
|
|
216
|
+
* @param cdp - CDP connection
|
|
217
|
+
* @param params - Selector and optional index
|
|
218
|
+
* @returns Measurements
|
|
219
|
+
* @throws CommandError (81) invalid selector, (91) page script failure
|
|
220
|
+
*/
|
|
221
|
+
async function measureSelector(cdp, params) {
|
|
222
|
+
const found = `(${DEEP_QUERY_JS})(${selectorArgsJS(params.selector)})`;
|
|
223
|
+
const response = (await cdp.send('Runtime.evaluate', {
|
|
224
|
+
expression: `(${LAYOUT_JS})(${found}, ${params.index ?? null}, ${LAYOUT_ELEMENT_LIMIT})`,
|
|
225
|
+
returnByValue: true,
|
|
226
|
+
}));
|
|
227
|
+
if (response.exceptionDetails) {
|
|
228
|
+
throwIfInvalidSelector(response.exceptionDetails, params.selector);
|
|
229
|
+
const err = operationFailedError('measure the elements', response.exceptionDetails.text);
|
|
230
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SCRIPT_ERROR);
|
|
231
|
+
}
|
|
232
|
+
return response.result.value;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Measure one exact element (from the query cache).
|
|
236
|
+
*
|
|
237
|
+
* @param cdp - CDP connection
|
|
238
|
+
* @param backendNodeId - Backend node id
|
|
239
|
+
* @param objectGroup - Object group for the handle
|
|
240
|
+
* @returns Measurements; no elements when the node left the page. An element
|
|
241
|
+
* of a cross-origin iframe (from an a11y query) is placed in the top-level
|
|
242
|
+
* viewport through the iframe's own measurements.
|
|
243
|
+
*/
|
|
244
|
+
async function measureNode(cdp, backendNodeId, objectGroup) {
|
|
245
|
+
const resolved = (await cdp
|
|
246
|
+
.send('DOM.resolveNode', { backendNodeId, objectGroup })
|
|
247
|
+
.catch((error) => {
|
|
248
|
+
log.debug(`Node ${backendNodeId} not resolved: ${getErrorMessage(error)}`);
|
|
249
|
+
return {};
|
|
250
|
+
}));
|
|
251
|
+
const objectId = resolved.object?.objectId;
|
|
252
|
+
if (!objectId)
|
|
253
|
+
return { count: 0, elements: [] };
|
|
254
|
+
const response = (await cdp.send('Runtime.callFunctionOn', {
|
|
255
|
+
objectId,
|
|
256
|
+
functionDeclaration: `function () { return (${LAYOUT_JS})(this.isConnected ? [this] : [], null, 1); }`,
|
|
257
|
+
returnByValue: true,
|
|
258
|
+
}));
|
|
259
|
+
const raw = response.result.value ?? { count: 0, elements: [] };
|
|
260
|
+
if (!raw.crossOriginFrame)
|
|
261
|
+
return raw;
|
|
262
|
+
const owner = await findFrameOwner(cdp, objectId);
|
|
263
|
+
if (owner === undefined)
|
|
264
|
+
return raw;
|
|
265
|
+
const ownerLayout = await measureNode(cdp, owner, objectGroup);
|
|
266
|
+
return placeInOwnerFrame(raw, ownerLayout, await measureFrameMapping(cdp, objectId));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Turn page-side measurements into the layout report: rounded page and
|
|
270
|
+
* viewport coordinates, viewport position and text preview per element.
|
|
271
|
+
*
|
|
272
|
+
* @param raw - Measurements with at least one element
|
|
273
|
+
* @param params - Request (selector and index)
|
|
274
|
+
* @returns Layout report
|
|
275
|
+
*/
|
|
276
|
+
export function buildLayoutResult(raw, params) {
|
|
277
|
+
const omitted = params.index === undefined ? raw.count - raw.elements.length : 0;
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
selector: params.selector,
|
|
281
|
+
count: raw.count,
|
|
282
|
+
page: roundPage(raw.page),
|
|
283
|
+
elements: raw.elements.map((element) => elementLayout(element, raw.page)),
|
|
284
|
+
...(omitted > 0 && { omitted }),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Page layout with whole pixels.
|
|
289
|
+
*
|
|
290
|
+
* @param page - Measured page layout
|
|
291
|
+
* @returns Rounded page layout
|
|
292
|
+
*/
|
|
293
|
+
function roundPage(page) {
|
|
294
|
+
return {
|
|
295
|
+
viewport: { width: Math.round(page.viewport.width), height: Math.round(page.viewport.height) },
|
|
296
|
+
scroll: { x: Math.round(page.scroll.x), y: Math.round(page.scroll.y) },
|
|
297
|
+
document: { width: Math.round(page.document.width), height: Math.round(page.document.height) },
|
|
298
|
+
...(page.colorScheme && { colorScheme: page.colorScheme }),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Layout of one element.
|
|
303
|
+
*
|
|
304
|
+
* @param raw - Element measurements
|
|
305
|
+
* @param page - Page layout (viewport size and scroll position)
|
|
306
|
+
* @returns Element layout; `coveredBy` only for elements that are in view
|
|
307
|
+
*/
|
|
308
|
+
function elementLayout(raw, page) {
|
|
309
|
+
const { rect } = raw.geometry;
|
|
310
|
+
const placement = classifyViewportPosition(raw.geometry, page.viewport);
|
|
311
|
+
const inView = placement.inViewport === 'visible' || placement.inViewport === 'partly';
|
|
312
|
+
const text = textPreview(raw.text);
|
|
313
|
+
return {
|
|
314
|
+
index: raw.index,
|
|
315
|
+
tag: raw.tag,
|
|
316
|
+
element: raw.element,
|
|
317
|
+
...(text && { text }),
|
|
318
|
+
...(raw.context && { context: raw.context }),
|
|
319
|
+
bounds: {
|
|
320
|
+
x: Math.round(rect.x + page.scroll.x),
|
|
321
|
+
y: Math.round(rect.y + page.scroll.y),
|
|
322
|
+
width: Math.round(rect.width),
|
|
323
|
+
height: Math.round(rect.height),
|
|
324
|
+
},
|
|
325
|
+
viewport: { x: Math.round(rect.x), y: Math.round(rect.y) },
|
|
326
|
+
...placement,
|
|
327
|
+
...(inView && raw.coveredBy && { coveredBy: raw.coveredBy }),
|
|
328
|
+
...(raw.geometry.invisible && { invisible: raw.geometry.invisible }),
|
|
329
|
+
...(raw.geometry.inert && { inert: true }),
|
|
330
|
+
computed: raw.computed,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page-side functions of `bdg dom listeners`: what only the page can tell
|
|
3
|
+
* about an element's listeners (handler names and identities, React root
|
|
4
|
+
* containers, the handlers behind jQuery's dispatcher, React's `on…` props
|
|
5
|
+
* of the element and its ancestors, the element's iframe).
|
|
6
|
+
*
|
|
7
|
+
* Each piece is a function expression in a string, combined into
|
|
8
|
+
* {@link ELEMENT_INFO_JS}; every lookup of page globals is guarded, so a
|
|
9
|
+
* page with odd globals (a throwing `jQuery` getter) only loses that detail.
|
|
10
|
+
*/
|
|
11
|
+
/** Most jQuery handlers resolved per call; the rest keep jQuery's dispatcher */
|
|
12
|
+
export declare const MAX_JQUERY_HANDLERS = 50;
|
|
13
|
+
/** Most React prop handlers resolved per call; the rest are counted */
|
|
14
|
+
export declare const MAX_REACT_HANDLERS = 50;
|
|
15
|
+
/**
|
|
16
|
+
* Page function, called on the element with the listeners (position in the
|
|
17
|
+
* chain and event type of each), the requested event types (null for all;
|
|
18
|
+
* React props are filtered before the limit), then the chain's objects, then each
|
|
19
|
+
* listener's handler, then the function each handler calls (a bound
|
|
20
|
+
* function's target, else null).
|
|
21
|
+
*
|
|
22
|
+
* Returns `[info, ...jQueryHandlers, ...reactHandlers]`. `info` has the
|
|
23
|
+
* iframe element holding the element's document (`frame`), a framework
|
|
24
|
+
* label per chain entry (`roots`), per listener the handler's name, the
|
|
25
|
+
* identity and name of the function it calls (equal identities are the same
|
|
26
|
+
* function object) and, for jQuery's dispatcher, the jQuery handlers that
|
|
27
|
+
* run for the element; then the React `on…` props that run for the
|
|
28
|
+
* element's events (`react`, see {@link REACT_HANDLERS_JS}). Their functions follow `info` in the same order.
|
|
29
|
+
* After {@link MAX_JQUERY_HANDLERS}, dispatchers stay unresolved and are
|
|
30
|
+
* counted in `jquerySkipped`; after {@link MAX_REACT_HANDLERS}, React props
|
|
31
|
+
* are counted in `reactSkipped`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ELEMENT_INFO_JS: string;
|
|
34
|
+
/** What {@link ELEMENT_INFO_JS} reports, by value */
|
|
35
|
+
export interface ElementInfo {
|
|
36
|
+
frame: string | null;
|
|
37
|
+
roots: Array<string | null>;
|
|
38
|
+
listeners: Array<{
|
|
39
|
+
name: string | null;
|
|
40
|
+
identity: number | null;
|
|
41
|
+
targetName: string | null;
|
|
42
|
+
jquery?: Array<{
|
|
43
|
+
type: string;
|
|
44
|
+
selector: string | null;
|
|
45
|
+
name: string;
|
|
46
|
+
}>;
|
|
47
|
+
}>;
|
|
48
|
+
/** jQuery handlers left unresolved (over the limit) */
|
|
49
|
+
jquerySkipped: number;
|
|
50
|
+
/** React `on…` props that run for the element's events, nearest first */
|
|
51
|
+
react: Array<{
|
|
52
|
+
/** Position of the element the prop is on in the chain (0 = the inspected element); null outside it (a portal's React parent) */
|
|
53
|
+
position: number | null;
|
|
54
|
+
/** Description of that element when it is outside the chain */
|
|
55
|
+
node?: string;
|
|
56
|
+
/** Prop name, e.g. `onClickCapture` */
|
|
57
|
+
prop: string;
|
|
58
|
+
/** DOM event type, e.g. `click` */
|
|
59
|
+
type: string;
|
|
60
|
+
capture: boolean;
|
|
61
|
+
name: string;
|
|
62
|
+
}>;
|
|
63
|
+
/** React props left unresolved (over the limit) */
|
|
64
|
+
reactSkipped: number;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=listenerPageScripts.d.ts.map
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page-side functions of `bdg dom listeners`: what only the page can tell
|
|
3
|
+
* about an element's listeners (handler names and identities, React root
|
|
4
|
+
* containers, the handlers behind jQuery's dispatcher, React's `on…` props
|
|
5
|
+
* of the element and its ancestors, the element's iframe).
|
|
6
|
+
*
|
|
7
|
+
* Each piece is a function expression in a string, combined into
|
|
8
|
+
* {@link ELEMENT_INFO_JS}; every lookup of page globals is guarded, so a
|
|
9
|
+
* page with odd globals (a throwing `jQuery` getter) only loses that detail.
|
|
10
|
+
*/
|
|
11
|
+
/** Most jQuery handlers resolved per call; the rest keep jQuery's dispatcher */
|
|
12
|
+
export const MAX_JQUERY_HANDLERS = 50;
|
|
13
|
+
/** Most React prop handlers resolved per call; the rest are counted */
|
|
14
|
+
export const MAX_REACT_HANDLERS = 50;
|
|
15
|
+
/** `(element, view) => string | null`: the iframe element holding the element's document */
|
|
16
|
+
const FRAME_OF_JS = `(element, view) => {
|
|
17
|
+
try {
|
|
18
|
+
const owner = view && view.frameElement;
|
|
19
|
+
return owner ? owner.tagName.toLowerCase() + (owner.id ? '#' + owner.id : '') : null;
|
|
20
|
+
} catch (e) { return null; }
|
|
21
|
+
}`;
|
|
22
|
+
/** `(node) => boolean`: whether the node is a React root container */
|
|
23
|
+
const REACT_ROOT_JS = `(node) => {
|
|
24
|
+
try {
|
|
25
|
+
return !!node && node.nodeType === 1 &&
|
|
26
|
+
('_reactRootContainer' in node || Object.keys(node).some((key) => key.startsWith('__reactContainer$')));
|
|
27
|
+
} catch (e) { return false; }
|
|
28
|
+
}`;
|
|
29
|
+
/** `(view) => jQuery | undefined`: the page's jQuery, when it has one */
|
|
30
|
+
const JQUERY_OF_JS = `(view) => {
|
|
31
|
+
try {
|
|
32
|
+
if (!view) return undefined;
|
|
33
|
+
return [view.jQuery, view.$].find((c) => c && typeof c._data === 'function');
|
|
34
|
+
} catch (e) { return undefined; }
|
|
35
|
+
}`;
|
|
36
|
+
/**
|
|
37
|
+
* `(jq, element, node, type, handler) => handleObj[] | null`: the jQuery
|
|
38
|
+
* handlers that run for `element` when `handler` is jQuery's dispatcher on
|
|
39
|
+
* `node` (delegates only when the element, or an element between it and
|
|
40
|
+
* `node`, matches their selector); null when it is not the dispatcher.
|
|
41
|
+
*/
|
|
42
|
+
const JQUERY_HANDLERS_JS = `(jq, element, node, type, handler) => {
|
|
43
|
+
try {
|
|
44
|
+
if (typeof handler !== 'function') return null;
|
|
45
|
+
const data = jq._data(node);
|
|
46
|
+
if (!data || data.handle !== handler || !data.events) return null;
|
|
47
|
+
const find = jq.find;
|
|
48
|
+
const matches = (el, selector) => {
|
|
49
|
+
try {
|
|
50
|
+
return find && typeof find.matchesSelector === 'function' ? find.matchesSelector(el, selector) : el.matches(selector);
|
|
51
|
+
} catch (e) { return false; }
|
|
52
|
+
};
|
|
53
|
+
const delegatedTo = (selector) => {
|
|
54
|
+
for (let el = element; el && el !== node; el = el.parentNode) {
|
|
55
|
+
if (el.nodeType === 1 && matches(el, selector)) return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
};
|
|
59
|
+
return Array.from(data.events[type] || []).filter((h) => !h.selector || delegatedTo(h.selector));
|
|
60
|
+
} catch (e) { return null; }
|
|
61
|
+
}`;
|
|
62
|
+
/**
|
|
63
|
+
* `(object, key) => value`: an own data property, read without running a
|
|
64
|
+
* getter the page may have defined (undefined for accessors and on errors).
|
|
65
|
+
*/
|
|
66
|
+
const OWN_VALUE_JS = `(object, key) => {
|
|
67
|
+
try {
|
|
68
|
+
const descriptor = object ? Object.getOwnPropertyDescriptor(object, key) : undefined;
|
|
69
|
+
return descriptor ? descriptor.value : undefined;
|
|
70
|
+
} catch (e) { return undefined; }
|
|
71
|
+
}`;
|
|
72
|
+
/**
|
|
73
|
+
* `(node, prefixes) => value`: the value of the node's own key starting with
|
|
74
|
+
* one of `prefixes` (React's keys end in a random suffix).
|
|
75
|
+
*/
|
|
76
|
+
const REACT_KEY_JS = `(node, prefixes) => {
|
|
77
|
+
try {
|
|
78
|
+
const key = Object.keys(node).find((k) => prefixes.some((prefix) => k.startsWith(prefix)));
|
|
79
|
+
return key === undefined ? undefined : (${OWN_VALUE_JS})(node, key);
|
|
80
|
+
} catch (e) { return undefined; }
|
|
81
|
+
}`;
|
|
82
|
+
/** `(node) => fiber | undefined`: React's fiber of a DOM element */
|
|
83
|
+
const REACT_FIBER_JS = `(node) => (${REACT_KEY_JS})(node, ['__reactFiber$', '__reactInternalInstance$'])`;
|
|
84
|
+
/**
|
|
85
|
+
* `(node) => object | null`: the props React rendered on a DOM element:
|
|
86
|
+
* `__reactProps$…` (React 17-19), `__reactEventHandlers$…` (React 16), else
|
|
87
|
+
* the `memoizedProps` of its fiber.
|
|
88
|
+
*/
|
|
89
|
+
const REACT_PROPS_JS = `(node) => {
|
|
90
|
+
if (!node || node.nodeType !== 1) return null;
|
|
91
|
+
const props = (${REACT_KEY_JS})(node, ['__reactProps$', '__reactEventHandlers$']);
|
|
92
|
+
if (props && typeof props === 'object') return props;
|
|
93
|
+
const memoized = (${OWN_VALUE_JS})((${REACT_FIBER_JS})(node), 'memoizedProps');
|
|
94
|
+
return memoized && typeof memoized === 'object' ? memoized : null;
|
|
95
|
+
}`;
|
|
96
|
+
/** Most fibers walked up from the element (guards against cycles) */
|
|
97
|
+
const MAX_FIBER_STEPS = 1000;
|
|
98
|
+
/**
|
|
99
|
+
* `(element, nodes) => Array<{ node, position }>`: the DOM elements whose
|
|
100
|
+
* React props run for events on the element, nearest first. With a fiber,
|
|
101
|
+
* the host components on its `.return` path (so a portal's React parents
|
|
102
|
+
* count and DOM parents outside the React path don't); `position` is the
|
|
103
|
+
* node's place in the chain, null outside it. Without one, the chain's elements.
|
|
104
|
+
*/
|
|
105
|
+
const REACT_HOSTS_JS = `(element, nodes) => {
|
|
106
|
+
const own = ${OWN_VALUE_JS};
|
|
107
|
+
let fiber = (${REACT_FIBER_JS})(element);
|
|
108
|
+
if (!fiber) return nodes.flatMap((node, position) => (node && node.nodeType === 1 ? [{ node, position }] : []));
|
|
109
|
+
const hosts = [];
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
for (let step = 0; fiber && typeof fiber === 'object' && !seen.has(fiber) && step < ${MAX_FIBER_STEPS}; step++) {
|
|
112
|
+
seen.add(fiber);
|
|
113
|
+
const node = own(fiber, 'stateNode');
|
|
114
|
+
if (typeof own(fiber, 'type') === 'string' && node && node.nodeType === 1) {
|
|
115
|
+
const position = nodes.indexOf(node);
|
|
116
|
+
hosts.push({ node, position: position === -1 ? null : position });
|
|
117
|
+
}
|
|
118
|
+
fiber = own(fiber, 'return');
|
|
119
|
+
}
|
|
120
|
+
return hosts;
|
|
121
|
+
}`;
|
|
122
|
+
/** `(node) => string`: CDP-like description of an element, e.g. `div#app.card` */
|
|
123
|
+
const DESCRIBE_NODE_JS = `(node) => {
|
|
124
|
+
try {
|
|
125
|
+
const classes = Array.from(node.classList || []).map((c) => '.' + c).join('');
|
|
126
|
+
return String(node.localName || 'element') + (node.id ? '#' + node.id : '') + classes;
|
|
127
|
+
} catch (e) { return 'element'; }
|
|
128
|
+
}`;
|
|
129
|
+
/**
|
|
130
|
+
* `(props) => Array<{ prop, handler, name }>`: the event handler props
|
|
131
|
+
* (`onClick`, `onKeyDownCapture`) among React props, read without
|
|
132
|
+
* running getters.
|
|
133
|
+
*/
|
|
134
|
+
const REACT_HANDLER_PROPS_JS = `(props) => {
|
|
135
|
+
const own = ${OWN_VALUE_JS};
|
|
136
|
+
try {
|
|
137
|
+
return Object.keys(props).filter((prop) => /^on[A-Z]/.test(prop)).flatMap((prop) => {
|
|
138
|
+
const handler = own(props, prop);
|
|
139
|
+
if (typeof handler !== 'function') return [];
|
|
140
|
+
const name = own(handler, 'name');
|
|
141
|
+
return [{ prop, handler, name: typeof name === 'string' ? name : '' }];
|
|
142
|
+
});
|
|
143
|
+
} catch (e) { return []; }
|
|
144
|
+
}`;
|
|
145
|
+
/**
|
|
146
|
+
* `(prop) => { type, capture }`: the DOM event type and phase of a React
|
|
147
|
+
* handler prop: `onClick` → click, `onClickCapture` → click in the capture
|
|
148
|
+
* phase, `onDoubleClick` → dblclick, `onFocus`/`onBlur` → focusin/focusout
|
|
149
|
+
* (the events React listens for), `onGotPointerCapture` → gotpointercapture
|
|
150
|
+
* (and `onGotPointerCaptureCapture` its capture phase).
|
|
151
|
+
*/
|
|
152
|
+
const REACT_EVENT_TYPE_JS = `(prop) => {
|
|
153
|
+
const base = prop.slice(2);
|
|
154
|
+
const capture = base.endsWith('Capture') && !/^(?:Got|Lost)PointerCapture$/.test(base);
|
|
155
|
+
const name = (capture ? base.slice(0, -7) : base).toLowerCase();
|
|
156
|
+
const types = { doubleclick: 'dblclick', focus: 'focusin', blur: 'focusout' };
|
|
157
|
+
return { type: types[name] || name, capture };
|
|
158
|
+
}`;
|
|
159
|
+
/**
|
|
160
|
+
* Event types React does not bubble to parents (their bubble-phase props on
|
|
161
|
+
* ancestors never run for the element): enter/leave, scroll (React 17+),
|
|
162
|
+
* and the events React listens for on the element itself (media, load, …).
|
|
163
|
+
*/
|
|
164
|
+
const REACT_NON_BUBBLING_TYPES = [
|
|
165
|
+
'mouseenter',
|
|
166
|
+
'mouseleave',
|
|
167
|
+
'pointerenter',
|
|
168
|
+
'pointerleave',
|
|
169
|
+
'scroll',
|
|
170
|
+
'scrollend',
|
|
171
|
+
'load',
|
|
172
|
+
'error',
|
|
173
|
+
'abort',
|
|
174
|
+
'cancel',
|
|
175
|
+
'close',
|
|
176
|
+
'invalid',
|
|
177
|
+
'toggle',
|
|
178
|
+
'canplay',
|
|
179
|
+
'canplaythrough',
|
|
180
|
+
'durationchange',
|
|
181
|
+
'emptied',
|
|
182
|
+
'encrypted',
|
|
183
|
+
'ended',
|
|
184
|
+
'loadeddata',
|
|
185
|
+
'loadedmetadata',
|
|
186
|
+
'loadstart',
|
|
187
|
+
'pause',
|
|
188
|
+
'play',
|
|
189
|
+
'playing',
|
|
190
|
+
'progress',
|
|
191
|
+
'ratechange',
|
|
192
|
+
'seeked',
|
|
193
|
+
'seeking',
|
|
194
|
+
'stalled',
|
|
195
|
+
'suspend',
|
|
196
|
+
'timeupdate',
|
|
197
|
+
'volumechange',
|
|
198
|
+
'waiting',
|
|
199
|
+
];
|
|
200
|
+
/**
|
|
201
|
+
* `(element, nodes, wanted, fns) => { react, reactSkipped }`: the React
|
|
202
|
+
* `on…` props that run for events on the element (its own, and bubbling or
|
|
203
|
+
* capture ones of its React parents), limited to the `wanted` types (all
|
|
204
|
+
* when null); their functions are pushed to `fns`.
|
|
205
|
+
*/
|
|
206
|
+
const REACT_HANDLERS_JS = `(element, nodes, wanted, fns) => {
|
|
207
|
+
const nonBubbling = new Set(${JSON.stringify(REACT_NON_BUBBLING_TYPES)});
|
|
208
|
+
const react = [];
|
|
209
|
+
let reactSkipped = 0;
|
|
210
|
+
(${REACT_HOSTS_JS})(element, nodes).forEach(({ node, position }, depth) => {
|
|
211
|
+
const props = (${REACT_PROPS_JS})(node);
|
|
212
|
+
if (!props) return;
|
|
213
|
+
for (const { prop, handler, name } of (${REACT_HANDLER_PROPS_JS})(props)) {
|
|
214
|
+
const { type, capture } = (${REACT_EVENT_TYPE_JS})(prop);
|
|
215
|
+
if ((wanted && !wanted.has(type)) || (depth > 0 && !capture && nonBubbling.has(type))) continue;
|
|
216
|
+
if (react.length >= ${MAX_REACT_HANDLERS}) { reactSkipped++; continue; }
|
|
217
|
+
fns.push(handler);
|
|
218
|
+
react.push({ position, ...(position === null && { node: (${DESCRIBE_NODE_JS})(node) }), prop, type, capture, name });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return { react, reactSkipped };
|
|
222
|
+
}`;
|
|
223
|
+
/**
|
|
224
|
+
* Page function, called on the element with the listeners (position in the
|
|
225
|
+
* chain and event type of each), the requested event types (null for all;
|
|
226
|
+
* React props are filtered before the limit), then the chain's objects, then each
|
|
227
|
+
* listener's handler, then the function each handler calls (a bound
|
|
228
|
+
* function's target, else null).
|
|
229
|
+
*
|
|
230
|
+
* Returns `[info, ...jQueryHandlers, ...reactHandlers]`. `info` has the
|
|
231
|
+
* iframe element holding the element's document (`frame`), a framework
|
|
232
|
+
* label per chain entry (`roots`), per listener the handler's name, the
|
|
233
|
+
* identity and name of the function it calls (equal identities are the same
|
|
234
|
+
* function object) and, for jQuery's dispatcher, the jQuery handlers that
|
|
235
|
+
* run for the element; then the React `on…` props that run for the
|
|
236
|
+
* element's events (`react`, see {@link REACT_HANDLERS_JS}). Their functions follow `info` in the same order.
|
|
237
|
+
* After {@link MAX_JQUERY_HANDLERS}, dispatchers stay unresolved and are
|
|
238
|
+
* counted in `jquerySkipped`; after {@link MAX_REACT_HANDLERS}, React props
|
|
239
|
+
* are counted in `reactSkipped`.
|
|
240
|
+
*/
|
|
241
|
+
export const ELEMENT_INFO_JS = `function (listeners, types, ...rest) {
|
|
242
|
+
const count = listeners.length;
|
|
243
|
+
const nodes = rest.slice(0, rest.length - 2 * count);
|
|
244
|
+
const handlers = rest.slice(nodes.length, nodes.length + count);
|
|
245
|
+
const targets = rest.slice(nodes.length + count);
|
|
246
|
+
const view = this.ownerDocument && this.ownerDocument.defaultView;
|
|
247
|
+
const jq = (${JQUERY_OF_JS})(view);
|
|
248
|
+
const ids = new Map();
|
|
249
|
+
const identity = (fn) => {
|
|
250
|
+
if (typeof fn !== 'function') return null;
|
|
251
|
+
if (!ids.has(fn)) ids.set(fn, ids.size);
|
|
252
|
+
return ids.get(fn);
|
|
253
|
+
};
|
|
254
|
+
const fns = [];
|
|
255
|
+
let jquerySkipped = 0;
|
|
256
|
+
const describeHandler = (h) => {
|
|
257
|
+
fns.push(h.handler);
|
|
258
|
+
return { type: h.origType || h.type, selector: h.selector || null,
|
|
259
|
+
name: typeof h.handler === 'function' ? h.handler.name : '' };
|
|
260
|
+
};
|
|
261
|
+
const info = listeners.map((listener, i) => {
|
|
262
|
+
const handler = handlers[i];
|
|
263
|
+
const target = typeof targets[i] === 'function' ? targets[i] : handler;
|
|
264
|
+
const entry = { name: typeof handler === 'function' ? handler.name : null, identity: identity(target),
|
|
265
|
+
targetName: typeof target === 'function' ? target.name : null };
|
|
266
|
+
const resolved = jq ? (${JQUERY_HANDLERS_JS})(jq, this, nodes[listener.position], listener.type, handler) : null;
|
|
267
|
+
if (!resolved) return entry;
|
|
268
|
+
if (fns.length + resolved.length > ${MAX_JQUERY_HANDLERS}) {
|
|
269
|
+
jquerySkipped += resolved.length;
|
|
270
|
+
return entry;
|
|
271
|
+
}
|
|
272
|
+
return { ...entry, jquery: resolved.map(describeHandler) };
|
|
273
|
+
});
|
|
274
|
+
const roots = nodes.map((node) => ((${REACT_ROOT_JS})(node) ? 'React root' : null));
|
|
275
|
+
const wanted = types && types.length > 0 ? new Set(types) : null;
|
|
276
|
+
const { react, reactSkipped } = (${REACT_HANDLERS_JS})(this, nodes, wanted, fns);
|
|
277
|
+
return [{ frame: (${FRAME_OF_JS})(this, view), roots, listeners: info, jquerySkipped, react, reactSkipped }, ...fns];
|
|
278
|
+
}`;
|
|
279
|
+
//# sourceMappingURL=listenerPageScripts.js.map
|