@crouton-kit/capture 1.3.7 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/bin/capture +2807 -1312
  2. package/package.json +4 -1
  3. package/src/capture.ts +10 -0
  4. package/src/cdp/args.ts +99 -10
  5. package/src/cdp/bridge/protocol.ts +99 -0
  6. package/src/cdp/bridge/server.ts +105 -43
  7. package/src/cdp/bridge/spawn.ts +62 -0
  8. package/src/cdp/commands/bridge-serve.ts +28 -3
  9. package/src/cdp/commands/cdp.ts +30 -2
  10. package/src/cdp/commands/measure/census.ts +31 -0
  11. package/src/cdp/commands/measure/check.ts +35 -0
  12. package/src/cdp/commands/measure/diff.ts +32 -0
  13. package/src/cdp/commands/measure/explain.ts +31 -0
  14. package/src/cdp/commands/measure/index.ts +114 -0
  15. package/src/cdp/commands/measure/map-focus.ts +24 -0
  16. package/src/cdp/commands/measure/map-layers.ts +25 -0
  17. package/src/cdp/commands/measure/map-scroll.ts +24 -0
  18. package/src/cdp/commands/measure/snap.ts +36 -0
  19. package/src/cdp/commands/measure/sweep.ts +30 -0
  20. package/src/cdp/commands/motion/index.ts +60 -0
  21. package/src/cdp/commands/motion/jank.ts +22 -0
  22. package/src/cdp/commands/motion/mask.ts +23 -0
  23. package/src/cdp/commands/motion/rec.ts +33 -0
  24. package/src/cdp/commands/motion/response.ts +26 -0
  25. package/src/cdp/commands/motion/timeline.ts +27 -0
  26. package/src/cdp/coordinates.ts +158 -0
  27. package/src/cdp/dispatch.ts +4 -0
  28. package/src/cdp/domains.ts +75 -0
  29. package/src/cdp/recorder-bridge.ts +521 -0
  30. package/src/cdp/source-map.ts +309 -0
  31. package/src/cdp/timing.ts +82 -0
  32. package/src/cdp/types.ts +66 -0
  33. package/src/cdp/usage.ts +2 -0
  34. package/src/output/artifact.ts +520 -0
  35. package/src/output/diff.ts +111 -0
  36. package/src/output/render.ts +593 -0
  37. package/src/output/selector.ts +185 -0
  38. package/src/session/artifacts.ts +229 -0
  39. package/src/session/commands.ts +119 -15
  40. package/src/session-context.ts +48 -8
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Shared quad math and frame/shadow coordinate stitching helpers for `measure snap`.
3
+ *
4
+ * These helpers are pure (`axisAlignedRectFromQuad`, `composeFrameTransform`,
5
+ * `toTopViewportQuad`) except `getContentQuadBox`, which calls the existing `CDPClient.send`
6
+ * directly (no new client abstraction). Quad helpers always preserve the original four
7
+ * points from CDP; an axis-aligned rect is only ever a derived convenience alongside them,
8
+ * never a replacement — several instruments (raster crops, hit-test, motion masks) need the
9
+ * real quad for transformed/rotated/wrapped elements.
10
+ */
11
+
12
+ import type { CDPClient } from './client.js';
13
+
14
+ /** Four corner points clockwise from top-left, in a single local frame's coordinate space: [x1,y1,x2,y2,x3,y3,x4,y4]. */
15
+ export type Quad = [number, number, number, number, number, number, number, number];
16
+
17
+ export interface Rect {
18
+ x: number;
19
+ y: number;
20
+ width: number;
21
+ height: number;
22
+ }
23
+
24
+ export interface BoxModelQuads {
25
+ content: Quad;
26
+ padding: Quad;
27
+ border: Quad;
28
+ margin: Quad;
29
+ width: number;
30
+ height: number;
31
+ }
32
+
33
+ export interface ContentQuadBox {
34
+ /**
35
+ * One quad per DOM fragment. Most elements produce exactly one; an inline element wrapped
36
+ * across lines (or an element split by column/fragmentation) produces more than one, and
37
+ * every fragment is preserved rather than collapsed into a single bounding rect.
38
+ */
39
+ quads: Quad[];
40
+ /** `DOM.getBoxModel`'s content/padding/border/margin quads plus width/height, in the same local coordinate space as `quads`. */
41
+ boxModel: BoxModelQuads;
42
+ }
43
+
44
+ /** Identifies a DOM node the way CDP's `DOM.getContentQuads`/`DOM.getBoxModel` accept it. */
45
+ export type NodeRef = { backendNodeId: number } | { nodeId: number } | { objectId: string };
46
+
47
+ /**
48
+ * Reads both the content quad(s) and the full box model (content/padding/border/margin) for
49
+ * one node via `DOM.getContentQuads` and `DOM.getBoxModel`. Coordinates are in the node's own
50
+ * frame's local viewport space — use `composeFrameTransform`/`toTopViewportQuad` to stitch
51
+ * iframe-nested coordinates into top-viewport space.
52
+ */
53
+ export async function getContentQuadBox(client: CDPClient, ref: NodeRef): Promise<ContentQuadBox> {
54
+ const [quadsResult, boxResult] = await Promise.all([
55
+ client.send('DOM.getContentQuads', ref as unknown as Record<string, unknown>) as Promise<{
56
+ quads: number[][];
57
+ }>,
58
+ client.send('DOM.getBoxModel', ref as unknown as Record<string, unknown>) as Promise<{
59
+ model: {
60
+ content: number[];
61
+ padding: number[];
62
+ border: number[];
63
+ margin: number[];
64
+ width: number;
65
+ height: number;
66
+ };
67
+ }>,
68
+ ]);
69
+
70
+ const model = boxResult.model;
71
+
72
+ return {
73
+ quads: quadsResult.quads.map(asQuad),
74
+ boxModel: {
75
+ content: asQuad(model.content),
76
+ padding: asQuad(model.padding),
77
+ border: asQuad(model.border),
78
+ margin: asQuad(model.margin),
79
+ width: model.width,
80
+ height: model.height,
81
+ },
82
+ };
83
+ }
84
+
85
+ function asQuad(points: number[]): Quad {
86
+ if (points.length !== 8) {
87
+ throw new Error(`Expected an 8-number quad (x1,y1,x2,y2,x3,y3,x4,y4), got ${points.length} numbers`);
88
+ }
89
+ return points as Quad;
90
+ }
91
+
92
+ /** Derives the axis-aligned bounding rect of a quad. Purely a convenience — `quad` itself stays the source of truth. */
93
+ export function axisAlignedRectFromQuad(quad: Quad): Rect {
94
+ const xs = [quad[0], quad[2], quad[4], quad[6]];
95
+ const ys = [quad[1], quad[3], quad[5], quad[7]];
96
+ const minX = Math.min(...xs);
97
+ const maxX = Math.max(...xs);
98
+ const minY = Math.min(...ys);
99
+ const maxY = Math.max(...ys);
100
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
101
+ }
102
+
103
+ /**
104
+ * One nested frame's offset: the translation (and optional scale, e.g. CSS zoom) of a child
105
+ * frame's content-box origin within its parent frame's local coordinate space.
106
+ */
107
+ export interface FrameOffset {
108
+ dx: number;
109
+ dy: number;
110
+ /** Defaults to 1 (no scale) when omitted. */
111
+ scaleX?: number;
112
+ scaleY?: number;
113
+ }
114
+
115
+ export interface FrameTransform {
116
+ dx: number;
117
+ dy: number;
118
+ scaleX: number;
119
+ scaleY: number;
120
+ }
121
+
122
+ /**
123
+ * Composes a chain of nested iframe offsets into one affine transform from the innermost
124
+ * (target) frame's local coordinates to top-viewport coordinates. `chain` is ordered from the
125
+ * outermost transition first (the top frame's offset into its direct child) to the innermost
126
+ * transition last (the target's immediate parent frame's offset into the target frame).
127
+ *
128
+ * Shadow DOM does not need this — shadow-root content shares its host element's coordinate
129
+ * space, so shadow-root traversal only affects which node a query resolves to, not its quad.
130
+ */
131
+ export function composeFrameTransform(chain: FrameOffset[]): FrameTransform {
132
+ let transform: FrameTransform = { dx: 0, dy: 0, scaleX: 1, scaleY: 1 };
133
+ for (const offset of chain) {
134
+ const scaleX = offset.scaleX ?? 1;
135
+ const scaleY = offset.scaleY ?? 1;
136
+ transform = {
137
+ dx: transform.dx + offset.dx * transform.scaleX,
138
+ dy: transform.dy + offset.dy * transform.scaleY,
139
+ scaleX: transform.scaleX * scaleX,
140
+ scaleY: transform.scaleY * scaleY,
141
+ };
142
+ }
143
+ return transform;
144
+ }
145
+
146
+ /** Applies a composed `FrameTransform` to every point of a quad, mapping it into top-viewport space. */
147
+ export function toTopViewportQuad(quad: Quad, transform: FrameTransform): Quad {
148
+ return [
149
+ quad[0] * transform.scaleX + transform.dx,
150
+ quad[1] * transform.scaleY + transform.dy,
151
+ quad[2] * transform.scaleX + transform.dx,
152
+ quad[3] * transform.scaleY + transform.dy,
153
+ quad[4] * transform.scaleX + transform.dx,
154
+ quad[5] * transform.scaleY + transform.dy,
155
+ quad[6] * transform.scaleX + transform.dx,
156
+ quad[7] * transform.scaleY + transform.dy,
157
+ ];
158
+ }
@@ -8,6 +8,8 @@ import { cmdHar } from './commands/har.js';
8
8
  import { cmdLib } from './commands/lib.js';
9
9
  import { cmdCdp } from './commands/cdp.js';
10
10
  import { cmdBridgeServe } from './commands/bridge-serve.js';
11
+ import { measureMain } from './commands/measure/index.js';
12
+ import { motionMain } from './commands/motion/index.js';
11
13
 
12
14
  export async function cdpMain(): Promise<void> {
13
15
  const args = process.argv.slice(2);
@@ -29,6 +31,8 @@ export async function cdpMain(): Promise<void> {
29
31
  case 'har': return cmdHar(parsed, args);
30
32
  case 'lib': return cmdLib(parsed, args);
31
33
  case 'cdp': return cmdCdp(parsed, args);
34
+ case 'measure': return measureMain(parsed, args);
35
+ case 'motion': return motionMain(parsed, args);
32
36
  case '__bridge-serve': return cmdBridgeServe(parsed, args);
33
37
  default:
34
38
  console.log(CDP_USAGE);
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Shared CDP domain enablement and reversible state-forcing helpers for `measure`/`motion`.
3
+ *
4
+ * Every helper here takes an already-connected `CDPClient` (see ./client.ts) and calls
5
+ * `client.send(...)` directly — there is no separate client abstraction. Domain `.enable`
6
+ * calls are idempotent in CDP, so callers may call these more than once per connection
7
+ * (e.g. once per collector) without ill effect.
8
+ */
9
+
10
+ import type { CDPClient } from './client.js';
11
+
12
+ /**
13
+ * Enables the CDP domains the `measure snap` substrate collectors need: frame/navigation
14
+ * metadata (Page), element geometry and tree access (DOM), computed style + winning-rule
15
+ * provenance (CSS — requires DOM.enable first), the accessibility tree (Accessibility),
16
+ * compositor layer facts (LayerTree), the animation inventory (Animation), and script
17
+ * execution for hit-test/text/form introspection (Runtime).
18
+ */
19
+ export async function enableDomainsForSnap(client: CDPClient): Promise<void> {
20
+ await client.send('Page.enable');
21
+ await client.send('DOM.enable');
22
+ await client.send('CSS.enable');
23
+ await Promise.all([
24
+ client.send('Accessibility.enable'),
25
+ client.send('LayerTree.enable'),
26
+ client.send('Animation.enable'),
27
+ client.send('Runtime.enable'),
28
+ ]);
29
+ }
30
+
31
+ /**
32
+ * Enables the CDP domains the motion recorder bridge needs: frame/navigation metadata and
33
+ * `Page.startScreencast` (Page), element geometry for rect/selector resolution (DOM), script
34
+ * execution for the injected Mutation/Resize/Performance observers (Runtime), request/response
35
+ * timing for `motion response` (Network), and playback-rate control for the animation inventory
36
+ * (Animation). `Tracing.start` is configured with its own category set by the recorder bridge
37
+ * (U13) and is not a plain `.enable` call, so it is not included here.
38
+ */
39
+ export async function enableDomainsForMotionRec(client: CDPClient): Promise<void> {
40
+ await client.send('Page.enable');
41
+ await client.send('DOM.enable');
42
+ await Promise.all([
43
+ client.send('Runtime.enable'),
44
+ client.send('Network.enable'),
45
+ client.send('Animation.enable'),
46
+ ]);
47
+ }
48
+
49
+ /**
50
+ * CSS pseudo-classes CDP can force directly via `CSS.forcePseudoState`. Real control states
51
+ * (checked/open/disabled/invalid) are not pseudo-classes in this sense — collectors toggle
52
+ * those with reversible DOM property writes and restore them after capture instead of using
53
+ * this helper.
54
+ */
55
+ export type ForcedPseudoClass =
56
+ | 'active'
57
+ | 'focus'
58
+ | 'focus-visible'
59
+ | 'focus-within'
60
+ | 'hover'
61
+ | 'target'
62
+ | 'visited';
63
+
64
+ /**
65
+ * Forces (or, called with an empty array, clears) a set of CSS pseudo-classes on a node.
66
+ * Requires `CSS.enable` (see `enableDomainsForSnap`). The caller is responsible for restoring
67
+ * state after capture by calling this again with `[]`.
68
+ */
69
+ export async function forcePseudoStateForNode(
70
+ client: CDPClient,
71
+ nodeId: number,
72
+ forcedPseudoClasses: ForcedPseudoClass[],
73
+ ): Promise<void> {
74
+ await client.send('CSS.forcePseudoState', { nodeId, forcedPseudoClasses });
75
+ }