@outcrawl/sdk 0.1.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 +198 -0
- package/dist/index.js +2493 -0
- package/dist/types/_deps/core/agent-alias.d.ts +219 -0
- package/dist/types/_deps/core/brand.d.ts +62 -0
- package/dist/types/_deps/core/certificate.d.ts +100 -0
- package/dist/types/_deps/core/cron.d.ts +116 -0
- package/dist/types/_deps/core/errors.d.ts +340 -0
- package/dist/types/_deps/core/index.d.ts +17 -0
- package/dist/types/_deps/core/money.d.ts +108 -0
- package/dist/types/_deps/core/registry.d.ts +466 -0
- package/dist/types/_deps/core/rules.d.ts +237 -0
- package/dist/types/_deps/core/secrets.d.ts +278 -0
- package/dist/types/_deps/core/types.d.ts +1677 -0
- package/dist/types/_deps/integrations/connector.d.ts +153 -0
- package/dist/types/_deps/replay/events.d.ts +1004 -0
- package/dist/types/agent.d.ts +313 -0
- package/dist/types/availability.d.ts +86 -0
- package/dist/types/browser.d.ts +107 -0
- package/dist/types/client.d.ts +128 -0
- package/dist/types/hands.d.ts +259 -0
- package/dist/types/index.d.ts +111 -0
- package/dist/types/integrations.d.ts +63 -0
- package/dist/types/monitors.d.ts +24 -0
- package/dist/types/page.d.ts +96 -0
- package/dist/types/profiles.d.ts +26 -0
- package/dist/types/result.d.ts +90 -0
- package/dist/types/rules.d.ts +41 -0
- package/dist/types/scrape.d.ts +68 -0
- package/dist/types/secrets.d.ts +36 -0
- package/dist/types/sessions.d.ts +124 -0
- package/dist/types/transport.d.ts +250 -0
- package/package.json +70 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outcrawl session replay - event model and wire constants.
|
|
3
|
+
*
|
|
4
|
+
* The format follows OpenReplay's shape, not rrweb's: **one full snapshot per
|
|
5
|
+
* frame, then pure deltas**. rrweb re-snapshots on a timer, and at roughly
|
|
6
|
+
* 500 KB per snapshot every five minutes those keyframes dominate stored bytes
|
|
7
|
+
* for a session that is otherwise a few kilobytes of mutations. Here a second
|
|
8
|
+
* snapshot is only ever emitted when the capture layer genuinely has no delta to
|
|
9
|
+
* describe what happened - a main-frame navigation, or a deliberate sparse
|
|
10
|
+
* checkpoint on a long session so scrubbing does not replay from zero
|
|
11
|
+
* (`index-seek.ts`).
|
|
12
|
+
*
|
|
13
|
+
* Three invariants run through every event.
|
|
14
|
+
*
|
|
15
|
+
* **Monotonic `t`.** Milliseconds since session start, integral, never
|
|
16
|
+
* decreasing. The encoder throws on a regression rather than writing bytes that
|
|
17
|
+
* would make the seek index lie.
|
|
18
|
+
*
|
|
19
|
+
* **Frame-qualified node ids.** Blink backend node ids are allocated per
|
|
20
|
+
* renderer process, so a site-isolated iframe hands out the same numbers as its
|
|
21
|
+
* parent. A bare `node: number` therefore aliases two different elements the
|
|
22
|
+
* moment a cross-origin iframe appears - which on real targets is immediately.
|
|
23
|
+
* Every reference to an existing node is a `NodeRef` of `{ frame, node }`.
|
|
24
|
+
*
|
|
25
|
+
* Inside a serialised DOM tree the ids stay bare, because a tree belongs to
|
|
26
|
+
* exactly one frame: `Snapshot.frame` for a snapshot, `parent.frame` for an
|
|
27
|
+
* inserted subtree. An `<iframe>` element carries `frame` pointing at the child
|
|
28
|
+
* frame whose own `Snapshot` holds its document; the element's `children` remain
|
|
29
|
+
* parent-frame nodes.
|
|
30
|
+
*
|
|
31
|
+
* **Tab-slabbed frame ordinals.** A recording is one BROWSER CONTEXT over a
|
|
32
|
+
* time range, not one page, and its tabs share one stream and one clock. Two
|
|
33
|
+
* renderers told to number their frames the same way would both call their main
|
|
34
|
+
* frame 0, and a player would fold one tab's mutations into another's tree. So
|
|
35
|
+
* tab k's ordinals come out of slab `k * FRAME_ORDINAL_SLAB` and `tabOf`
|
|
36
|
+
* inverts it - which makes tab identity a property of a field every event
|
|
37
|
+
* already carries rather than a field added to all fifteen of them.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Bump on ANY incompatible change, including edits to `STATIC_STRINGS` in
|
|
41
|
+
* `dictionary.ts` - the dictionary is as much part of the wire contract as the
|
|
42
|
+
* field order is.
|
|
43
|
+
*
|
|
44
|
+
* APPENDING AN EVENT TYPE IS NOT ONE, and `SubresourceCapture: 11` was added
|
|
45
|
+
* without a bump on purpose. `readFileHeader` rejects any version it does not
|
|
46
|
+
* equal exactly, so bumping is itself the breaking change here: it would
|
|
47
|
+
* orphan every session already in storage, which is a strictly worse outcome
|
|
48
|
+
* than the one it protects against. A new type at the END of the enum leaves
|
|
49
|
+
* every existing stream byte-identical and decoding identically, and the only
|
|
50
|
+
* incompatible direction - an old decoder meeting a new stream - does not
|
|
51
|
+
* exist as a deployment, because the encoder, the decoder and the player ship
|
|
52
|
+
* as one artifact.
|
|
53
|
+
*
|
|
54
|
+
* Editing `STATIC_STRINGS` is still a bump, and is why nothing was added to it
|
|
55
|
+
* for assets: dictionary indices shift, and every stored stream reinterprets
|
|
56
|
+
* its string table.
|
|
57
|
+
*
|
|
58
|
+
* ## TAB IDENTITY DID NOT BUMP IT EITHER, and this is the case worth reading
|
|
59
|
+
*
|
|
60
|
+
* Making a recording the whole browser context rather than one page is a
|
|
61
|
+
* change in KIND, not another event type, so it was decided against the corpus
|
|
62
|
+
* rather than by precedent. Three things had to hold and all three do.
|
|
63
|
+
*
|
|
64
|
+
* Identity rides a field that already exists. Tab k's frames live in slab
|
|
65
|
+
* `k * FRAME_ORDINAL_SLAB`, so the per-event frame varint - written once per
|
|
66
|
+
* event since version 1 and elided for ordinal 0 by `TYPE_FRAME_ZERO` -
|
|
67
|
+
* already says which tab an event belongs to. Nothing was added to any of the
|
|
68
|
+
* fifteen events to carry it. A per-event `tab` field was the obvious version
|
|
69
|
+
* and was refused: a byte on every event of a 12,000-event session to restate
|
|
70
|
+
* a number the stream already holds.
|
|
71
|
+
*
|
|
72
|
+
* An absent tab means tab 0 WITHOUT A READER APPLYING A DEFAULT. Every session
|
|
73
|
+
* recorded before the capture layer could attach a second tab has every
|
|
74
|
+
* ordinal below one slab, so `tabOf` answers 0 for all of them by arithmetic.
|
|
75
|
+
* A reader therefore never asks for a field a stored session cannot have,
|
|
76
|
+
* which is the requirement a version-gated field would have failed.
|
|
77
|
+
*
|
|
78
|
+
* `Console` and `Network` gained a `frame` - the one place identity was NOT
|
|
79
|
+
* free, because the encoder wrote a hard 0 for both. It is written into the
|
|
80
|
+
* varint slot those types already elided, so the byte layout of every stored
|
|
81
|
+
* session is unchanged and they decode to tab 0.
|
|
82
|
+
*/
|
|
83
|
+
export declare const FORMAT_VERSION = 1;
|
|
84
|
+
/** `OCRP`. Session stream magic. */
|
|
85
|
+
export declare const FILE_MAGIC: Uint8Array<ArrayBuffer>;
|
|
86
|
+
/** `SG`. Segment frame magic, so a truncated stream can be resynchronised. */
|
|
87
|
+
export declare const SEGMENT_MAGIC: Uint8Array<ArrayBuffer>;
|
|
88
|
+
/** magic(4) + version(1) + flags(1). Fixed width so segment 0 is at a known offset. */
|
|
89
|
+
export declare const FILE_HEADER_BYTES = 6;
|
|
90
|
+
/** magic(2) + payloadLength(u32le). Fixed width so an index scan can hop frames. */
|
|
91
|
+
export declare const SEGMENT_HEADER_BYTES = 6;
|
|
92
|
+
/** Segment flag bits. */
|
|
93
|
+
export declare const SegmentFlags: {
|
|
94
|
+
/**
|
|
95
|
+
* The segment opens with a main-frame `Snapshot`, so playback may start here
|
|
96
|
+
* without decoding anything earlier. The capture layer is responsible for
|
|
97
|
+
* emitting snapshots for every other live frame within the same segment.
|
|
98
|
+
*/
|
|
99
|
+
readonly Checkpoint: 1;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Every event's type byte carries this bit when the event's frame is 0, letting
|
|
103
|
+
* the frame varint be omitted entirely. The main frame is the overwhelming
|
|
104
|
+
* majority of events, and this is the cheapest byte in the format to remove.
|
|
105
|
+
*
|
|
106
|
+
* Safe because `EventType` needs four bits and will not grow to 128 members.
|
|
107
|
+
*/
|
|
108
|
+
export declare const TYPE_FRAME_ZERO = 128;
|
|
109
|
+
/**
|
|
110
|
+
* Width of one tab's frame-ordinal space, and therefore how a tab is
|
|
111
|
+
* identified on the wire.
|
|
112
|
+
*
|
|
113
|
+
* ONE RECORDING IS ONE BROWSER CONTEXT, not one page. Every tab of a session
|
|
114
|
+
* records into this stream under one session id, and every renderer numbers
|
|
115
|
+
* its own frames from whatever the capture command was told. Told the same
|
|
116
|
+
* thing twice, two tabs both claim main-frame ordinal 0 and both allocate
|
|
117
|
+
* subframes from 1 - so a player applies tab B's mutations to tab A's tree and
|
|
118
|
+
* reconstructs a page that never existed. That is a replay which is not merely
|
|
119
|
+
* incomplete but confidently WRONG, and durable recordings get believed.
|
|
120
|
+
*
|
|
121
|
+
* So tab k is armed in its own slab: its main frame is `k * FRAME_ORDINAL_SLAB`
|
|
122
|
+
* and its renderer mints same-process subframes from one above that. TAB
|
|
123
|
+
* IDENTITY IS THEREFORE ALREADY IN EVERY EVENT, at no cost: the frame ordinal
|
|
124
|
+
* is written once per event and `TYPE_FRAME_ZERO` elides it entirely for tab
|
|
125
|
+
* 0's main frame. A per-event `tab` field would restate a number the `frame`
|
|
126
|
+
* varint already carries.
|
|
127
|
+
*
|
|
128
|
+
* THIS IS WHY AN ABSENT TAB MEANS TAB 0 WITHOUT A DEFAULT BEING APPLIED. Every
|
|
129
|
+
* session recorded before the capture layer could attach a second tab has
|
|
130
|
+
* every ordinal below one slab, so {@link tabOf} answers 0 for all of them by
|
|
131
|
+
* arithmetic rather than by a reader's convention - and a reader never has to
|
|
132
|
+
* ask for a field a stored session cannot have.
|
|
133
|
+
*
|
|
134
|
+
* 65536 and not 1024: `frame` is varint-encoded per event, so a 2^16 slab
|
|
135
|
+
* costs three varint bytes instead of one on every event of tab 1 and up,
|
|
136
|
+
* which is a few KB on a real session and compresses to almost nothing because
|
|
137
|
+
* it is the same three bytes every time. A 1024-wide slab would save that and
|
|
138
|
+
* put a page with 1023 same-process iframes into the next tab's id space,
|
|
139
|
+
* which is silent DOM corruption rather than a size regression.
|
|
140
|
+
*
|
|
141
|
+
* Defined here rather than in the capture layer because it is a WIRE fact: a
|
|
142
|
+
* decoder that did not know it could not tell a viewer which tab an event
|
|
143
|
+
* belongs to, and two copies of it are two chances to disagree.
|
|
144
|
+
*/
|
|
145
|
+
export declare const FRAME_ORDINAL_SLAB = 65536;
|
|
146
|
+
/**
|
|
147
|
+
* Which tab a frame ordinal belongs to. The only way to ask.
|
|
148
|
+
*
|
|
149
|
+
* `>>> 16` rather than a division by {@link FRAME_ORDINAL_SLAB}: the slab is
|
|
150
|
+
* 2^16 and every ordinal is a non-negative integer well below 2^31, so the
|
|
151
|
+
* shift is exact and cheaper. It exists as a function so that the shift is
|
|
152
|
+
* written once - one open-coded `>>> 16` against a slab that later changes
|
|
153
|
+
* width is a whole tab of events silently attributed to its neighbour.
|
|
154
|
+
*/
|
|
155
|
+
export declare function tabOf(frame: number): number;
|
|
156
|
+
/** A tab's main-frame ordinal: its identity, as every `TabEvent` carries it. */
|
|
157
|
+
export declare function frameBaseOf(tab: number): number;
|
|
158
|
+
/**
|
|
159
|
+
* String encoding: one varint header, low bit says which kind.
|
|
160
|
+
*
|
|
161
|
+
* - `header & 1` set - inline. `header >> 1` is the UTF-8 byte length, and those
|
|
162
|
+
* bytes follow. The string is then appended to the segment's dynamic table.
|
|
163
|
+
* - `header & 1` clear - reference. `header >> 1` indexes the static dictionary
|
|
164
|
+
* below `STATIC_COUNT`, and the segment's dynamic table above it.
|
|
165
|
+
*
|
|
166
|
+
* The dynamic table is never transmitted: both sides append every inline string
|
|
167
|
+
* they see, in the same order, so the table builds itself. That makes a
|
|
168
|
+
* one-shot string cost exactly what inlining it would have cost - no table entry
|
|
169
|
+
* plus index to pay for it - which in turn means every string in the format can
|
|
170
|
+
* be run through the table, text nodes and input values included, with no
|
|
171
|
+
* downside when they turn out to be unique.
|
|
172
|
+
*/
|
|
173
|
+
export declare const STRING_INLINE = 1;
|
|
174
|
+
/**
|
|
175
|
+
* `MouseMove` batch modes. Pointer bursts are the highest-volume event class in
|
|
176
|
+
* a session and their per-sample deltas need five or six bits, so the packed
|
|
177
|
+
* mode declares one bit width for the batch and packs samples at that width.
|
|
178
|
+
* `MOUSE_WIDE` is the fallback for a batch too wide to pack, which keeps the
|
|
179
|
+
* packing unconditional rather than a bet on well-behaved input.
|
|
180
|
+
*/
|
|
181
|
+
export declare const MOUSE_WIDE = 0;
|
|
182
|
+
export declare const MOUSE_PACKED = 1;
|
|
183
|
+
/**
|
|
184
|
+
* Widest bit field the packed mode will use.
|
|
185
|
+
*
|
|
186
|
+
* Two widths share one byte as nibbles, so 15 is the ceiling. That covers a
|
|
187
|
+
* jump of +-16,383 pixels or milliseconds between consecutive samples, which no
|
|
188
|
+
* real pointer stream reaches - and anything that does falls back to
|
|
189
|
+
* `MOUSE_WIDE` rather than failing.
|
|
190
|
+
*/
|
|
191
|
+
export declare const MAX_PACKED_BITS = 15;
|
|
192
|
+
/**
|
|
193
|
+
* Presence bits for optional fields.
|
|
194
|
+
*
|
|
195
|
+
* Every event or node with optional fields writes one flags byte before its
|
|
196
|
+
* fixed fields. A byte shared by up to eight optionals beats a per-field
|
|
197
|
+
* presence marker, and putting the bits here rather than privately in
|
|
198
|
+
* `encoder.ts` and `decoder.ts` makes encoder/decoder drift - the failure mode
|
|
199
|
+
* that produces plausible-looking garbage instead of an error - impossible.
|
|
200
|
+
*
|
|
201
|
+
* Tri-state booleans need two bits: one that the field was present at all, one
|
|
202
|
+
* for its value, so `masked: false` survives the round trip distinctly from an
|
|
203
|
+
* absent `masked`.
|
|
204
|
+
*/
|
|
205
|
+
export declare const SnapshotFlags: {
|
|
206
|
+
readonly Parent: 1;
|
|
207
|
+
readonly Adopted: 2;
|
|
208
|
+
};
|
|
209
|
+
export declare const ElementFlags: {
|
|
210
|
+
readonly Namespace: 1;
|
|
211
|
+
readonly Shadow: 2;
|
|
212
|
+
readonly Frame: 4;
|
|
213
|
+
};
|
|
214
|
+
export declare const ShadowFlags: {
|
|
215
|
+
readonly Adopted: 1;
|
|
216
|
+
};
|
|
217
|
+
export declare const InsertFlags: {
|
|
218
|
+
readonly Previous: 1;
|
|
219
|
+
};
|
|
220
|
+
export declare const AttrFlags: {
|
|
221
|
+
readonly Value: 1;
|
|
222
|
+
};
|
|
223
|
+
export declare const StyleFlags: {
|
|
224
|
+
readonly SourceUrl: 1;
|
|
225
|
+
readonly Owner: 2;
|
|
226
|
+
readonly DisabledSet: 4;
|
|
227
|
+
readonly DisabledValue: 8;
|
|
228
|
+
};
|
|
229
|
+
export declare const InputFlags: {
|
|
230
|
+
readonly Value: 1;
|
|
231
|
+
readonly CheckedSet: 2;
|
|
232
|
+
readonly CheckedValue: 4;
|
|
233
|
+
readonly MaskedSet: 8;
|
|
234
|
+
readonly MaskedValue: 16;
|
|
235
|
+
};
|
|
236
|
+
export declare const AssetFlags: {
|
|
237
|
+
readonly ContentType: 1;
|
|
238
|
+
readonly Dropped: 2;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Presence bits for {@link RegionFilmEvent}, deliberately the same two as
|
|
242
|
+
* {@link AssetFlags} and deliberately a separate constant.
|
|
243
|
+
*
|
|
244
|
+
* The same two optionals happen to be optional on both events for the same
|
|
245
|
+
* reason - a body can be refused and a container can be unknown - but a film
|
|
246
|
+
* segment and a subresource are different events whose optional sets will
|
|
247
|
+
* diverge, and sharing one constant is how the bit that was added for one
|
|
248
|
+
* silently changes the meaning of the other's byte.
|
|
249
|
+
*/
|
|
250
|
+
export declare const FilmFlags: {
|
|
251
|
+
readonly ContentType: 1;
|
|
252
|
+
readonly Dropped: 2;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Presence bits for {@link ConsoleEvent}.
|
|
256
|
+
*
|
|
257
|
+
* `MainFrame` is a VALUE bit rather than a presence bit, and it is here rather
|
|
258
|
+
* than in a byte of its own because the flags byte is already being spent. The
|
|
259
|
+
* field is not optional: every console line either came from the frame the
|
|
260
|
+
* session is attached to or from one below it, and "unknown" is not a state the
|
|
261
|
+
* browser can be in about a message it just routed.
|
|
262
|
+
*/
|
|
263
|
+
export declare const ConsoleFlags: {
|
|
264
|
+
readonly Url: 1;
|
|
265
|
+
readonly Line: 2;
|
|
266
|
+
readonly MainFrame: 4;
|
|
267
|
+
};
|
|
268
|
+
/** Presence bits for {@link NetworkEvent}. */
|
|
269
|
+
export declare const NetworkFlags: {
|
|
270
|
+
readonly Method: 1;
|
|
271
|
+
readonly Status: 2;
|
|
272
|
+
readonly MimeType: 4;
|
|
273
|
+
readonly EncodedBytes: 8;
|
|
274
|
+
readonly DurationMs: 16;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Presence bits for {@link TabEvent}.
|
|
278
|
+
*
|
|
279
|
+
* In PDL order - `url` then `title` - because that is the order
|
|
280
|
+
* `Outcrawl.sessionTarget` declares them in and the order the codec writes
|
|
281
|
+
* them, so the three read top to bottom together.
|
|
282
|
+
*
|
|
283
|
+
* All three are genuinely optional and absence is a CLAIM: "this phase is not
|
|
284
|
+
* about the url" is a different statement from an empty url, and the
|
|
285
|
+
* difference is a `Close` wiping the label off a strip chip. A reader applies
|
|
286
|
+
* what is present and keeps what it had.
|
|
287
|
+
*/
|
|
288
|
+
export declare const TabFlags: {
|
|
289
|
+
readonly Url: 1;
|
|
290
|
+
readonly Title: 2;
|
|
291
|
+
readonly Opener: 4;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Console severity, as the browser can actually distinguish it.
|
|
295
|
+
*
|
|
296
|
+
* FOUR VALUES, AND THERE IS NO `info`. `ThreadDebuggerCommonImpl::
|
|
297
|
+
* V8MessageLevelToMessageLevel` maps v8's `kMessageLog` and `kMessageInfo` onto
|
|
298
|
+
* one `mojom::ConsoleMessageLevel`, so `console.info` is not distinguishable
|
|
299
|
+
* from `console.log` on any in-process route -- the collapse is in Blink,
|
|
300
|
+
* upstream of every tap. A fifth member here would be a distinction the data
|
|
301
|
+
* does not carry, which is the same reason `Outcrawl.sessionConsole` does not
|
|
302
|
+
* declare one either.
|
|
303
|
+
*
|
|
304
|
+
* Numeric rather than the protocol's strings because the set is closed by
|
|
305
|
+
* Blink's own enum and a byte beats interning four literals.
|
|
306
|
+
*/
|
|
307
|
+
export declare const ConsoleLevel: {
|
|
308
|
+
readonly Log: 0;
|
|
309
|
+
readonly Warn: 1;
|
|
310
|
+
readonly Error: 2;
|
|
311
|
+
readonly Debug: 3;
|
|
312
|
+
};
|
|
313
|
+
export type ConsoleLevel = (typeof ConsoleLevel)[keyof typeof ConsoleLevel];
|
|
314
|
+
/**
|
|
315
|
+
* Which end of one resource load this row is.
|
|
316
|
+
*
|
|
317
|
+
* TWO ROWS PER COMPLETED LOAD, both emitted when the load ENDS, because
|
|
318
|
+
* `WebContentsImpl::ResourceLoadComplete` is the funnel and it fires once. The
|
|
319
|
+
* pair is what makes a request visible as open-and-unanswered when the playhead
|
|
320
|
+
* sits between them.
|
|
321
|
+
*
|
|
322
|
+
* `Failed` is a NET error, not an HTTP one: a 404 is a `Response` with `status`
|
|
323
|
+
* 404, exactly as the CDP path reported it.
|
|
324
|
+
*/
|
|
325
|
+
export declare const NetworkPhase: {
|
|
326
|
+
readonly Request: 0;
|
|
327
|
+
readonly Response: 1;
|
|
328
|
+
readonly Failed: 2;
|
|
329
|
+
};
|
|
330
|
+
export type NetworkPhase = (typeof NetworkPhase)[keyof typeof NetworkPhase];
|
|
331
|
+
export declare const EventType: {
|
|
332
|
+
readonly Snapshot: 0;
|
|
333
|
+
readonly NodeInserted: 1;
|
|
334
|
+
readonly NodeRemoved: 2;
|
|
335
|
+
readonly AttrChanged: 3;
|
|
336
|
+
readonly TextChanged: 4;
|
|
337
|
+
readonly StyleSheetAdded: 5;
|
|
338
|
+
readonly Navigation: 6;
|
|
339
|
+
readonly Input: 7;
|
|
340
|
+
readonly Scroll: 8;
|
|
341
|
+
readonly MouseMove: 9;
|
|
342
|
+
readonly Viewport: 10;
|
|
343
|
+
readonly SubresourceCapture: 11;
|
|
344
|
+
readonly RegionFilm: 12;
|
|
345
|
+
readonly Console: 13;
|
|
346
|
+
readonly Network: 14;
|
|
347
|
+
readonly Tab: 15;
|
|
348
|
+
};
|
|
349
|
+
export type EventType = (typeof EventType)[keyof typeof EventType];
|
|
350
|
+
/** Indexed by `EventType`, for error messages and debugging. */
|
|
351
|
+
export declare const EVENT_NAMES: readonly string[];
|
|
352
|
+
/**
|
|
353
|
+
* Why an asset's bytes are NOT in the bundle.
|
|
354
|
+
*
|
|
355
|
+
* Present on a {@link SubresourceCaptureEvent} whose body was refused. The
|
|
356
|
+
* entry is still emitted, and that is the whole point: a replay that says what
|
|
357
|
+
* it dropped beats one that lies. A player renders a labelled placeholder and
|
|
358
|
+
* strips the dead reference, so the viewer's browser never reaches for an
|
|
359
|
+
* origin it cannot get to.
|
|
360
|
+
*
|
|
361
|
+
* An asset with NO entry at all is a third case and means no claim either way
|
|
362
|
+
* - every session recorded before this event existed has none, and a player
|
|
363
|
+
* must leave those references untouched rather than make them worse.
|
|
364
|
+
*/
|
|
365
|
+
export declare const DropReason: {
|
|
366
|
+
/** Bigger than the per-asset ceiling on its own. */
|
|
367
|
+
readonly TooLarge: 1;
|
|
368
|
+
/** The session's byte budget was already spent. */
|
|
369
|
+
readonly BudgetExhausted: 2;
|
|
370
|
+
/** The load failed, or its bytes could not be read back. */
|
|
371
|
+
readonly FetchFailed: 3;
|
|
372
|
+
};
|
|
373
|
+
export type DropReason = (typeof DropReason)[keyof typeof DropReason];
|
|
374
|
+
/** DOM `nodeType` values, reused so the wire matches the platform. */
|
|
375
|
+
export declare const NodeKind: {
|
|
376
|
+
readonly Element: 1;
|
|
377
|
+
readonly Text: 3;
|
|
378
|
+
readonly Comment: 8;
|
|
379
|
+
readonly Document: 9;
|
|
380
|
+
readonly Doctype: 10;
|
|
381
|
+
};
|
|
382
|
+
export type NodeKind = (typeof NodeKind)[keyof typeof NodeKind];
|
|
383
|
+
export declare const NavKind: {
|
|
384
|
+
/** A new document committed; a `Snapshot` for the frame follows. */
|
|
385
|
+
readonly Commit: 0;
|
|
386
|
+
/** History API or fragment change; the DOM survives, no snapshot follows. */
|
|
387
|
+
readonly SameDocument: 1;
|
|
388
|
+
readonly BackForward: 2;
|
|
389
|
+
readonly Reload: 3;
|
|
390
|
+
};
|
|
391
|
+
export type NavKind = (typeof NavKind)[keyof typeof NavKind];
|
|
392
|
+
/**
|
|
393
|
+
* Which step in a tab's life a {@link TabEvent} reports.
|
|
394
|
+
*
|
|
395
|
+
* FOUR KINDS, and each one exists because the fact is NOT derivable from the
|
|
396
|
+
* event stream itself.
|
|
397
|
+
*
|
|
398
|
+
* `Open` is not derivable because a tab's first `Snapshot` arrives one CDP
|
|
399
|
+
* round trip after the tab existed, and a tab that opens and is closed before
|
|
400
|
+
* it commits a document produces no snapshot at all.
|
|
401
|
+
*
|
|
402
|
+
* `Close` is not derivable at all: a stream that stops carrying a slab's
|
|
403
|
+
* events is indistinguishable from a tab sitting idle in the background, and
|
|
404
|
+
* on a real session most background tabs are idle.
|
|
405
|
+
*
|
|
406
|
+
* `Focus` is not derivable because nothing about a document says whether the
|
|
407
|
+
* browser was SHOWING it, and "what the browser showed" is the focused tab.
|
|
408
|
+
*
|
|
409
|
+
* `Info` is not derivable because a title is not in the DOM stream's frame at
|
|
410
|
+
* all - `<title>`'s text node is, but the title Blink resolved from it is a
|
|
411
|
+
* browser-side fact - and because a url a player can label a chip with must
|
|
412
|
+
* survive a `Close` that carries none.
|
|
413
|
+
*/
|
|
414
|
+
export declare const TabKind: {
|
|
415
|
+
/** This tab now exists. Emitted before its first `Snapshot`. */
|
|
416
|
+
readonly Open: 0;
|
|
417
|
+
/** This tab is gone. Nothing later belongs to it. */
|
|
418
|
+
readonly Close: 1;
|
|
419
|
+
/** This tab became the one the browser was showing. */
|
|
420
|
+
readonly Focus: 2;
|
|
421
|
+
/** Its url, its title, or both changed. */
|
|
422
|
+
readonly Info: 3;
|
|
423
|
+
};
|
|
424
|
+
export type TabKind = (typeof TabKind)[keyof typeof TabKind];
|
|
425
|
+
/**
|
|
426
|
+
* A reference to a node that already exists in the reconstructed DOM.
|
|
427
|
+
*
|
|
428
|
+
* `frame` is a capture-assigned frame ordinal (0 is the main frame), stable for
|
|
429
|
+
* the life of the frame. `node` is the Blink backend node id, unique only within
|
|
430
|
+
* that frame.
|
|
431
|
+
*/
|
|
432
|
+
export interface NodeRef {
|
|
433
|
+
frame: number;
|
|
434
|
+
node: number;
|
|
435
|
+
}
|
|
436
|
+
/** An attribute, kept as an ordered pair so attribute order survives the round trip. */
|
|
437
|
+
export interface Attr {
|
|
438
|
+
name: string;
|
|
439
|
+
value: string;
|
|
440
|
+
}
|
|
441
|
+
export interface ElementNode {
|
|
442
|
+
kind: typeof NodeKind.Element;
|
|
443
|
+
node: number;
|
|
444
|
+
/** `nodeName`: upper case for HTML elements, authored case for SVG and MathML. */
|
|
445
|
+
tag: string;
|
|
446
|
+
attrs: Attr[];
|
|
447
|
+
children: DomNode[];
|
|
448
|
+
/** Set only outside the XHTML namespace. */
|
|
449
|
+
namespace?: string;
|
|
450
|
+
/** Attached shadow root, open or closed - we read it from C++, so closed is visible. */
|
|
451
|
+
shadow?: ShadowRootNode;
|
|
452
|
+
/** For `<iframe>`/`<frame>`/`<object>`: the child frame whose `Snapshot` holds its document. */
|
|
453
|
+
frame?: number;
|
|
454
|
+
}
|
|
455
|
+
export interface TextNode {
|
|
456
|
+
kind: typeof NodeKind.Text;
|
|
457
|
+
node: number;
|
|
458
|
+
text: string;
|
|
459
|
+
}
|
|
460
|
+
export interface CommentNode {
|
|
461
|
+
kind: typeof NodeKind.Comment;
|
|
462
|
+
node: number;
|
|
463
|
+
text: string;
|
|
464
|
+
}
|
|
465
|
+
export interface DocumentNode {
|
|
466
|
+
kind: typeof NodeKind.Document;
|
|
467
|
+
node: number;
|
|
468
|
+
children: DomNode[];
|
|
469
|
+
}
|
|
470
|
+
export interface DoctypeNode {
|
|
471
|
+
kind: typeof NodeKind.Doctype;
|
|
472
|
+
node: number;
|
|
473
|
+
name: string;
|
|
474
|
+
publicId: string;
|
|
475
|
+
systemId: string;
|
|
476
|
+
}
|
|
477
|
+
export type DomNode = ElementNode | TextNode | CommentNode | DocumentNode | DoctypeNode;
|
|
478
|
+
/**
|
|
479
|
+
* A shadow root. Not a member of `DomNode` because it cannot appear as a child;
|
|
480
|
+
* it hangs off its host element's `shadow`.
|
|
481
|
+
*/
|
|
482
|
+
export interface ShadowRootNode {
|
|
483
|
+
node: number;
|
|
484
|
+
mode: 'open' | 'closed';
|
|
485
|
+
children: DomNode[];
|
|
486
|
+
/**
|
|
487
|
+
* `adoptedStyleSheets`, as stylesheet ids from `StyleSheetAdded`. Ids rather
|
|
488
|
+
* than text because adopted sheets are genuinely shared objects - the same
|
|
489
|
+
* sheet adopted into 200 shadow roots is one `StyleSheetAdded` and 200
|
|
490
|
+
* references.
|
|
491
|
+
*/
|
|
492
|
+
adopted?: number[];
|
|
493
|
+
}
|
|
494
|
+
export interface ViewportSize {
|
|
495
|
+
width: number;
|
|
496
|
+
height: number;
|
|
497
|
+
/** `devicePixelRatio`. Fractional, so it goes on the wire as `f64`. */
|
|
498
|
+
dpr: number;
|
|
499
|
+
}
|
|
500
|
+
/** A batched pointer sample. `dt` is milliseconds after the carrying event's `t`. */
|
|
501
|
+
export interface MousePosition {
|
|
502
|
+
x: number;
|
|
503
|
+
y: number;
|
|
504
|
+
dt: number;
|
|
505
|
+
}
|
|
506
|
+
interface EventBase {
|
|
507
|
+
/** Milliseconds since session start. Integral and non-decreasing across the session. */
|
|
508
|
+
t: number;
|
|
509
|
+
}
|
|
510
|
+
/** The full DOM of one frame. First event of the session, and of every checkpoint. */
|
|
511
|
+
export interface SnapshotEvent extends EventBase {
|
|
512
|
+
type: typeof EventType.Snapshot;
|
|
513
|
+
frame: number;
|
|
514
|
+
url: string;
|
|
515
|
+
root: DomNode;
|
|
516
|
+
viewport: ViewportSize;
|
|
517
|
+
/** Parent frame ordinal. Absent exactly for the main frame. */
|
|
518
|
+
parent?: number;
|
|
519
|
+
/** Document-level `adoptedStyleSheets`, as `StyleSheetAdded` ids. */
|
|
520
|
+
adopted?: number[];
|
|
521
|
+
}
|
|
522
|
+
export interface NodeInsertedEvent extends EventBase {
|
|
523
|
+
type: typeof EventType.NodeInserted;
|
|
524
|
+
parent: NodeRef;
|
|
525
|
+
/** Insert after this sibling. Absent means the subtree became the first child. */
|
|
526
|
+
previous?: NodeRef;
|
|
527
|
+
/** The inserted subtree, in `parent.frame`. */
|
|
528
|
+
node: DomNode;
|
|
529
|
+
}
|
|
530
|
+
export interface NodeRemovedEvent extends EventBase {
|
|
531
|
+
type: typeof EventType.NodeRemoved;
|
|
532
|
+
parent: NodeRef;
|
|
533
|
+
target: NodeRef;
|
|
534
|
+
}
|
|
535
|
+
export interface AttrChangedEvent extends EventBase {
|
|
536
|
+
type: typeof EventType.AttrChanged;
|
|
537
|
+
target: NodeRef;
|
|
538
|
+
name: string;
|
|
539
|
+
/** Absent means the attribute was removed. */
|
|
540
|
+
value?: string;
|
|
541
|
+
}
|
|
542
|
+
export interface TextChangedEvent extends EventBase {
|
|
543
|
+
type: typeof EventType.TextChanged;
|
|
544
|
+
target: NodeRef;
|
|
545
|
+
text: string;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* A stylesheet became active: `<style>`, `<link>`, or a constructed sheet pushed
|
|
549
|
+
* into `adoptedStyleSheets`. The last of these is invisible to rrweb, which is
|
|
550
|
+
* one of the reasons its replays of component-heavy sites render unstyled.
|
|
551
|
+
*/
|
|
552
|
+
export interface StyleSheetAddedEvent extends EventBase {
|
|
553
|
+
type: typeof EventType.StyleSheetAdded;
|
|
554
|
+
frame: number;
|
|
555
|
+
/** Stylesheet id, referenced by `adopted` lists. Unique within the frame. */
|
|
556
|
+
sheet: number;
|
|
557
|
+
text: string;
|
|
558
|
+
sourceUrl?: string;
|
|
559
|
+
/** Owner `<style>` or `<link>`. Absent for constructed sheets, which have none. */
|
|
560
|
+
owner?: NodeRef;
|
|
561
|
+
disabled?: boolean;
|
|
562
|
+
}
|
|
563
|
+
export interface NavigationEvent extends EventBase {
|
|
564
|
+
type: typeof EventType.Navigation;
|
|
565
|
+
frame: number;
|
|
566
|
+
url: string;
|
|
567
|
+
kind: NavKind;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* A form control's value changed.
|
|
571
|
+
*
|
|
572
|
+
* `masked` records that the capture layer redacted the value, so a player can
|
|
573
|
+
* render "hidden" rather than silently showing an empty field.
|
|
574
|
+
*/
|
|
575
|
+
export interface InputEvent extends EventBase {
|
|
576
|
+
type: typeof EventType.Input;
|
|
577
|
+
target: NodeRef;
|
|
578
|
+
value?: string;
|
|
579
|
+
checked?: boolean;
|
|
580
|
+
masked?: boolean;
|
|
581
|
+
}
|
|
582
|
+
/** `target` is the scrolling element, or the frame's document node for the viewport. */
|
|
583
|
+
export interface ScrollEvent extends EventBase {
|
|
584
|
+
type: typeof EventType.Scroll;
|
|
585
|
+
target: NodeRef;
|
|
586
|
+
x: number;
|
|
587
|
+
y: number;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Pointer movement, batched. One event per burst rather than one per sample: at
|
|
591
|
+
* 60 Hz an unbatched stream is the single largest contributor to a session's
|
|
592
|
+
* byte count, and the samples compress far better as deltas within one array.
|
|
593
|
+
*/
|
|
594
|
+
export interface MouseMoveEvent extends EventBase {
|
|
595
|
+
type: typeof EventType.MouseMove;
|
|
596
|
+
frame: number;
|
|
597
|
+
positions: MousePosition[];
|
|
598
|
+
}
|
|
599
|
+
export interface ViewportEvent extends EventBase {
|
|
600
|
+
type: typeof EventType.Viewport;
|
|
601
|
+
frame: number;
|
|
602
|
+
viewport: ViewportSize;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* One subresource the renderer actually loaded, and the content hash its bytes
|
|
606
|
+
* are stored under.
|
|
607
|
+
*
|
|
608
|
+
* Identity only. The bytes are NOT here and must not be: a 2 MB image inside a
|
|
609
|
+
* segment sized for DOM deltas breaks both the 64 KB segment ceiling and the
|
|
610
|
+
* one-coalesced-ranged-GET-per-chunk seek path. Bodies live in their own
|
|
611
|
+
* objects, addressed by {@link hash}; this event is what tells a player which
|
|
612
|
+
* hash a URL resolved to at a given time.
|
|
613
|
+
*
|
|
614
|
+
* WHY THE BROWSER EMITS THIS AND A CLIENT CANNOT SYNTHESISE IT
|
|
615
|
+
*
|
|
616
|
+
* Only the renderer knows what it fetched. Walking the DOM and CSS for
|
|
617
|
+
* references over-captures fonts by 9x on a measured real page - both
|
|
618
|
+
* `@font-face` blocks on books.toscrape.com list eot, woff, ttf and svg, and
|
|
619
|
+
* Chromium takes the woff and never asks for the rest, so a parser bills
|
|
620
|
+
* 613,107 B where the browser spent 66,996 B. It also under-captures, because
|
|
621
|
+
* `srcset`, `image-set()` and lazy loading are resolved against a viewport and
|
|
622
|
+
* a device pixel ratio that only the renderer has. This is the same boundary
|
|
623
|
+
* the fork already draws for pointer timing, in the other direction: whoever
|
|
624
|
+
* knows the answer owns it.
|
|
625
|
+
*
|
|
626
|
+
* `url` is normalised by `assetKey`, which is also what a player calls on a
|
|
627
|
+
* CSS `url()` token, so the two sides cannot disagree about the key.
|
|
628
|
+
*/
|
|
629
|
+
export interface SubresourceCaptureEvent extends EventBase {
|
|
630
|
+
type: typeof EventType.SubresourceCapture;
|
|
631
|
+
frame: number;
|
|
632
|
+
/** Absolute, fragment-stripped, `assetKey`-normalised request URL. */
|
|
633
|
+
url: string;
|
|
634
|
+
/**
|
|
635
|
+
* Lowercase hex, 32 chars: SHA-256 truncated to 128 bits.
|
|
636
|
+
*
|
|
637
|
+
* A string rather than 16 raw bytes for two reasons that both cost bytes to
|
|
638
|
+
* ignore. It is usable as a `Map` key by value, which is how a player folds
|
|
639
|
+
* a URL table and caches one decoded body per hash. And it goes through the
|
|
640
|
+
* segment's string table, so an asset restated at every checkpoint - which a
|
|
641
|
+
* checkpoint owes, exactly as it owes stylesheets - costs its 32 characters
|
|
642
|
+
* once and a table reference every time after.
|
|
643
|
+
*
|
|
644
|
+
* Truncated to 128 bits because this addresses one session's assets, where
|
|
645
|
+
* the birthday bound at a million assets is around 1e-27.
|
|
646
|
+
*/
|
|
647
|
+
hash: string;
|
|
648
|
+
/** Bytes of the body as loaded. Present even when `dropped` is set. */
|
|
649
|
+
byteLength: number;
|
|
650
|
+
/** From the response. Absent when the load failed before headers. */
|
|
651
|
+
contentType?: string;
|
|
652
|
+
/** Set when the bytes are NOT in the bundle. See {@link DropReason}. */
|
|
653
|
+
dropped?: DropReason;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* One encoded run of film over one region the DOM stream structurally cannot
|
|
657
|
+
* carry: an accelerated canvas, a WebGL context, a `<video>`.
|
|
658
|
+
*
|
|
659
|
+
* ## Why this is in the DOM stream at all
|
|
660
|
+
*
|
|
661
|
+
* Because a replay is one timeline. Those elements record a
|
|
662
|
+
* `ForeignLayerDisplayItem` wrapping a `cc::Layer` whose pixels live in GPU
|
|
663
|
+
* textures, so painting produces drawing commands for everything around them
|
|
664
|
+
* and nothing for them - which is why the fork reports their rects in
|
|
665
|
+
* `uncaptured` rather than pretending, and why `Outcrawl.startRegionFilm`
|
|
666
|
+
* exists to film exactly those rects. A film delivered beside the recording
|
|
667
|
+
* would be a second artifact with a second clock, and the two would drift the
|
|
668
|
+
* moment anybody seeked. Here the run is an event: it has the session's `t`,
|
|
669
|
+
* it lands in a segment, the seek index covers it, a checkpoint restates it,
|
|
670
|
+
* and the same retention sweep removes it.
|
|
671
|
+
*
|
|
672
|
+
* Identity only, exactly like {@link SubresourceCaptureEvent}: the H.264 bytes
|
|
673
|
+
* live in the session's asset store under {@link hash} and are fetched through
|
|
674
|
+
* the reader's `asset()` once per body. Frames as stills would have been the
|
|
675
|
+
* simpler wire and it was measured to be the wrong one - individual WebP at
|
|
676
|
+
* quality 40 costs 17.65 MB/min against 4.43 MB/min for H.264 at crf 34 and
|
|
677
|
+
* 5 fps on a full-viewport animating 3D scene, because consecutive frames of
|
|
678
|
+
* one region are nearly identical and a bag of stills throws away the only
|
|
679
|
+
* compression that matters.
|
|
680
|
+
*/
|
|
681
|
+
export interface RegionFilmEvent extends EventBase {
|
|
682
|
+
type: typeof EventType.RegionFilm;
|
|
683
|
+
frame: number;
|
|
684
|
+
/**
|
|
685
|
+
* `"canvas"` or `"video"`: the renderer's own label, the same string from
|
|
686
|
+
* the same predicate that fills `Outcrawl.UncapturedRegion.kind`.
|
|
687
|
+
*
|
|
688
|
+
* A string rather than a union so a browser that learns a third kind parses
|
|
689
|
+
* here instead of failing.
|
|
690
|
+
*/
|
|
691
|
+
kind: string;
|
|
692
|
+
/** Where the run belongs on the page, in the frame's own coordinates. */
|
|
693
|
+
x: number;
|
|
694
|
+
y: number;
|
|
695
|
+
width: number;
|
|
696
|
+
height: number;
|
|
697
|
+
/**
|
|
698
|
+
* The readback's OWN pixel dimensions, which are NOT {@link width} and
|
|
699
|
+
* {@link height} whenever a canvas backing store is not 1:1 with its layout
|
|
700
|
+
* box - a device pixel ratio above one, or `width`/`height` attributes that
|
|
701
|
+
* disagree with the CSS box.
|
|
702
|
+
*
|
|
703
|
+
* Carried so a player scales the film onto the rect. Assuming the two pairs
|
|
704
|
+
* are equal presents a crop of a region as the whole region, which is the
|
|
705
|
+
* same class of lie as drawing white where a canvas was.
|
|
706
|
+
*/
|
|
707
|
+
imageWidth: number;
|
|
708
|
+
imageHeight: number;
|
|
709
|
+
/** Content hash of the encoded run. Same shape and store as an asset's. */
|
|
710
|
+
hash: string;
|
|
711
|
+
/** Bytes of the encoded run. Present even when `dropped` is set. */
|
|
712
|
+
byteLength: number;
|
|
713
|
+
/**
|
|
714
|
+
* Session time of the run's FIRST FRAME, which is not `t` and cannot be.
|
|
715
|
+
*
|
|
716
|
+
* A run is encoded once it is complete, so its event necessarily enters the
|
|
717
|
+
* stream after its last frame — and `t` is non-decreasing across the
|
|
718
|
+
* session, so the event cannot be stamped back at the moment the filming
|
|
719
|
+
* started. `t` is therefore where the run BECOMES KNOWN and `startedAt` is
|
|
720
|
+
* what it covers, and a player composites the run while
|
|
721
|
+
* `startedAt <= playhead < startedAt + durationMs`, seeking the video to
|
|
722
|
+
* `playhead - startedAt`.
|
|
723
|
+
*
|
|
724
|
+
* The consequence is worth stating rather than discovering: playing forward
|
|
725
|
+
* across a canvas that has just appeared, the region is blank until its
|
|
726
|
+
* first run lands, which is why the recorder bounds a run's length instead
|
|
727
|
+
* of letting one canvas film for a whole session. After a SEEK there is no
|
|
728
|
+
* lag at all, because the checkpoint restates every run it already knows.
|
|
729
|
+
*
|
|
730
|
+
* On the wire this is `t - startedAt`, a small varint on first emission and
|
|
731
|
+
* a growing one at each restatement, rather than a second absolute
|
|
732
|
+
* millisecond count.
|
|
733
|
+
*/
|
|
734
|
+
startedAt: number;
|
|
735
|
+
/**
|
|
736
|
+
* How long the run covers, from {@link startedAt}. Measured across the
|
|
737
|
+
* browser's own frame timestamps rather than from a nominal frame rate:
|
|
738
|
+
* encoding at the requested fps turned a 12.22 s segment into 6.6 s of
|
|
739
|
+
* video, and a 1:1 seek then ran out of film and held the last frame while
|
|
740
|
+
* everything else kept moving.
|
|
741
|
+
*/
|
|
742
|
+
durationMs: number;
|
|
743
|
+
/** Frames in the run. With `durationMs`, the rate actually achieved. */
|
|
744
|
+
frames: number;
|
|
745
|
+
/** Container of the encoded run. `video/mp4` for everything shipped today. */
|
|
746
|
+
contentType?: string;
|
|
747
|
+
/** Set when the bytes are NOT in the bundle. See {@link DropReason}. */
|
|
748
|
+
dropped?: DropReason;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* One console line the page produced, from `Outcrawl.sessionConsole`.
|
|
752
|
+
*
|
|
753
|
+
* ## THIS IS A LOG, NOT STATE, AND THAT IS THE WHOLE DESIGN
|
|
754
|
+
*
|
|
755
|
+
* Every other event in this format folds into `ReconstructState`: it says what
|
|
756
|
+
* the page IS at `t`. A console line says what HAPPENED at `t` and is never
|
|
757
|
+
* unsaid. So `applyEvent` folds nothing for it, a `Snapshot` does not clear it
|
|
758
|
+
* the way it clears its frame's tables, and a checkpoint does not restate it.
|
|
759
|
+
*
|
|
760
|
+
* Stated at length because the frame model is the one a reader will assume, and
|
|
761
|
+
* assuming it here produces the opposite of a fix. Restating a cumulative
|
|
762
|
+
* console at every checkpoint is unbounded: the asset table a checkpoint owes
|
|
763
|
+
* back is forty entries and a session's console is however many lines the page
|
|
764
|
+
* chose to print. The alternative -- a bounded ring buffer in state -- would
|
|
765
|
+
* make a seek and a play-through agree only by throwing away the lines a
|
|
766
|
+
* customer opened the replay to read.
|
|
767
|
+
*
|
|
768
|
+
* The consumer is therefore a WHOLE-SESSION pass rather than the fold:
|
|
769
|
+
* `buildTimeline` in `@outcrawl/player` reads every event once and turns an
|
|
770
|
+
* `Error` line into a prominent scrub-bar mark, and an export carries these
|
|
771
|
+
* verbatim in its segments for anything decoding the stream directly.
|
|
772
|
+
*
|
|
773
|
+
* ## Why it is in this stream at all
|
|
774
|
+
*
|
|
775
|
+
* The same argument {@link RegionFilmEvent} makes: a replay is one timeline. A
|
|
776
|
+
* console log delivered beside the recording is a second artifact with a second
|
|
777
|
+
* clock, and the two drift the moment anybody seeks. Here the line has the
|
|
778
|
+
* session's `t`, it lands in a segment, the seek index covers it and the same
|
|
779
|
+
* retention sweep removes it.
|
|
780
|
+
*
|
|
781
|
+
* ## What this route does not carry, so nobody looks for it
|
|
782
|
+
*
|
|
783
|
+
* `console.log`'s object arguments. V8 flattens them with
|
|
784
|
+
* `V8ValueStringBuilder` before the only embedder-visible callback fires, and
|
|
785
|
+
* that flattener renders a plain object through `ObjectProtoToString` -- so
|
|
786
|
+
* `console.log('x', {a:1})` arrives as `x [object Object]`. The CDP route is no
|
|
787
|
+
* better: `V8RuntimeAgentImpl::enable` replays pre-enable messages with
|
|
788
|
+
* `generatePreview=false`. Shared and service workers are also absent, having
|
|
789
|
+
* no parent frame to proxy through, and so are `kNetwork`-source messages,
|
|
790
|
+
* which `FrameConsole::ReportMessageToClient` drops before the browser sees
|
|
791
|
+
* them.
|
|
792
|
+
*/
|
|
793
|
+
export interface ConsoleEvent extends EventBase {
|
|
794
|
+
type: typeof EventType.Console;
|
|
795
|
+
level: ConsoleLevel;
|
|
796
|
+
text: string;
|
|
797
|
+
/**
|
|
798
|
+
* WHICH TAB said it: the emitting tab's main-frame ordinal, per
|
|
799
|
+
* {@link FRAME_ORDINAL_SLAB}. `tabOf(frame)` is the tab.
|
|
800
|
+
*
|
|
801
|
+
* A TAB ATTRIBUTION AND NOT A FRAME ONE, which is why {@link frameId} is
|
|
802
|
+
* still here beside it. The frame that produced the line may be an
|
|
803
|
+
* out-of-process iframe several levels down, and the browser reports it by
|
|
804
|
+
* token rather than by any ordinal this stream assigned - so this field
|
|
805
|
+
* answers "whose console pane does this belong in", not "which document".
|
|
806
|
+
*
|
|
807
|
+
* Zero means tab 0, and it means it for every session ever recorded: the
|
|
808
|
+
* encoder elides this varint entirely for ordinal 0 through
|
|
809
|
+
* `TYPE_FRAME_ZERO`, so a stored stream that predates multi-tab capture
|
|
810
|
+
* decodes to 0 here without a byte changing and without a reader applying a
|
|
811
|
+
* default. Before this field existed the encoder wrote a hard 0 for every
|
|
812
|
+
* console line, which on a multi-tab session stacked five tabs' diagnostics
|
|
813
|
+
* onto tab 0 - complete-looking and wrong, which is worse than a gap.
|
|
814
|
+
*/
|
|
815
|
+
frame: number;
|
|
816
|
+
/**
|
|
817
|
+
* `Page.frameId` of the frame that produced it: the `devtools_frame_token`,
|
|
818
|
+
* NOT this stream's frame ordinal.
|
|
819
|
+
*
|
|
820
|
+
* The two are different namespaces and cannot be reconciled here. An ordinal
|
|
821
|
+
* is assigned by whichever renderer's capture emitted a DOM event; this token
|
|
822
|
+
* is the browser's per-frame identity, and the browser is the only process
|
|
823
|
+
* that sees every frame. Carried as the browser's own string so it joins
|
|
824
|
+
* against `getFrameOrdinal` and against a client's own frame lifecycle, and
|
|
825
|
+
* so it is never mistaken for something the fold could index.
|
|
826
|
+
*/
|
|
827
|
+
frameId: string;
|
|
828
|
+
/** False for any subframe, including an out-of-process one. */
|
|
829
|
+
mainFrame: boolean;
|
|
830
|
+
/**
|
|
831
|
+
* The script URL Blink attributed the message to. Absent when it had none,
|
|
832
|
+
* which is what a platform-raised message with no script in scope looks like.
|
|
833
|
+
*/
|
|
834
|
+
url?: string;
|
|
835
|
+
/**
|
|
836
|
+
* One-based, as Blink counts. Absent when unknown and NEVER defaulted to 1: a
|
|
837
|
+
* wrong line is worse than no line for anybody counting lines in a source
|
|
838
|
+
* file.
|
|
839
|
+
*/
|
|
840
|
+
line?: number;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* One end of one resource load, from `Outcrawl.sessionNetwork`.
|
|
844
|
+
*
|
|
845
|
+
* A log and not state, for the reasons {@link ConsoleEvent} sets out at length;
|
|
846
|
+
* the fold ignores it and `buildTimeline` marks a {@link NetworkPhase.Failed}
|
|
847
|
+
* row on the scrub bar.
|
|
848
|
+
*
|
|
849
|
+
* ## `at` IS THE MEASUREMENT AND `t` IS ONLY WHEN WE LEARNED IT
|
|
850
|
+
*
|
|
851
|
+
* `ResourceLoadComplete` fires when a load ENDS, so both of a load's rows are
|
|
852
|
+
* produced at that instant however long the load took. Stamping the `Request`
|
|
853
|
+
* row at that instant would put every request on the timeline at the moment it
|
|
854
|
+
* finished, which is wrong about the one thing a network pane exists to show --
|
|
855
|
+
* and it cannot simply be stamped earlier, because `t` is non-decreasing across
|
|
856
|
+
* the session and the stream has already moved on.
|
|
857
|
+
*
|
|
858
|
+
* So this carries both, exactly as {@link RegionFilmEvent} carries `startedAt`
|
|
859
|
+
* beside `t` and for the same reason. `t` is where the row became known;
|
|
860
|
+
* `at` is the instant it describes -- `load_timing_info.request_start` for a
|
|
861
|
+
* `Request`, browser arrival for a `Response` or a `Failed`. A viewer places
|
|
862
|
+
* the row at `at`. `at <= t` always, and the wire spends the difference rather
|
|
863
|
+
* than a second absolute millisecond count.
|
|
864
|
+
*
|
|
865
|
+
* A load with no net-level timing at all -- a DNS or connect failure -- has
|
|
866
|
+
* `at` equal to arrival, because there is nothing else to date it to.
|
|
867
|
+
*
|
|
868
|
+
* ## What is missing from this route, stated rather than discovered
|
|
869
|
+
*
|
|
870
|
+
* Cancelled loads: a request still in flight when the page navigates away is
|
|
871
|
+
* reported through `NotifyResourceLoadCanceled`, which the renderer handles
|
|
872
|
+
* renderer-side only, so nothing reaches the browser and no row is emitted.
|
|
873
|
+
* Per-hop redirect detail: one load produces one `Request` and one closing row,
|
|
874
|
+
* with the started-with URL on the first and the ended-on URL on the second, so
|
|
875
|
+
* a redirect shows as the URL changing across the pair -- `blink.mojom
|
|
876
|
+
* .RedirectInfo` carries no full URL to report the hops with. And response
|
|
877
|
+
* bodies, on any route, deliberately: `ResourceLoadInfo` does not carry one and
|
|
878
|
+
* the body pipe never reaches the browser process.
|
|
879
|
+
*/
|
|
880
|
+
export interface NetworkEvent extends EventBase {
|
|
881
|
+
type: typeof EventType.Network;
|
|
882
|
+
phase: NetworkPhase;
|
|
883
|
+
/**
|
|
884
|
+
* `<childProcessId>.<requestId>`, the shape `IdentifiersFactory::RequestId`
|
|
885
|
+
* gives a CDP `Network.requestId`, so a row here is joinable against a
|
|
886
|
+
* protocol log. A memory-cache hit arrives with no request id of any kind and
|
|
887
|
+
* is reported as `memcache.<n>`, which cannot collide.
|
|
888
|
+
*/
|
|
889
|
+
requestId: string;
|
|
890
|
+
/**
|
|
891
|
+
* The URL of THIS row: what the load was started with on `Request`, what it
|
|
892
|
+
* ended on when closing. See the redirect note above.
|
|
893
|
+
*/
|
|
894
|
+
url: string;
|
|
895
|
+
/**
|
|
896
|
+
* WHICH TAB loaded it: the emitting tab's main-frame ordinal. See
|
|
897
|
+
* {@link ConsoleEvent.frame}, which this is exactly - including that 0 is
|
|
898
|
+
* tab 0 for every stored session at no wire cost.
|
|
899
|
+
*/
|
|
900
|
+
frame: number;
|
|
901
|
+
/** `devtools_frame_token`, not a frame ordinal. See {@link ConsoleEvent.frameId}. */
|
|
902
|
+
frameId: string;
|
|
903
|
+
/** Session milliseconds of the instant this row describes. Integral, `<= t`. */
|
|
904
|
+
at: number;
|
|
905
|
+
/**
|
|
906
|
+
* The request method, post-redirect: the notifier overwrites it on each hop
|
|
907
|
+
* and no field could hold both. Absent only on a memory-cache hit.
|
|
908
|
+
*/
|
|
909
|
+
method?: string;
|
|
910
|
+
/**
|
|
911
|
+
* HTTP status. Absent on `Failed`, matching the CDP fold, and absent when
|
|
912
|
+
* there were no headers at all. Every optional here is ABSENT rather than
|
|
913
|
+
* zero when the route did not carry it: a status of 0, a byte count of 0 and
|
|
914
|
+
* a duration of 0 all read as measurements and none of them would be one.
|
|
915
|
+
*/
|
|
916
|
+
status?: number;
|
|
917
|
+
mimeType?: string;
|
|
918
|
+
/** On the wire after TLS and proxying: CDP's `encodedDataLength`. Integral. */
|
|
919
|
+
encodedBytes?: number;
|
|
920
|
+
/** Request start to browser arrival, on the closing row. Integral. */
|
|
921
|
+
durationMs?: number;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* One step in one tab's life: it opened, it closed, it became the tab the
|
|
925
|
+
* browser was showing, or its url and title changed.
|
|
926
|
+
*
|
|
927
|
+
* ## Why the context needs events at all
|
|
928
|
+
*
|
|
929
|
+
* A recording is one BROWSER CONTEXT over a time range, not one page, and it is
|
|
930
|
+
* played back on ONE CLOCK: scrubbing to `t` shows which tabs existed at `t`,
|
|
931
|
+
* which was focused, and what each contained. A tab that opened at 12.4s does
|
|
932
|
+
* not exist before 12.4s and a tab closed at 30.1s is gone after it, whether
|
|
933
|
+
* the viewer is playing or seeking across the instant.
|
|
934
|
+
*
|
|
935
|
+
* Every DOM event already says which tab it belongs to for free -
|
|
936
|
+
* {@link FRAME_ORDINAL_SLAB} - so this event carries only what the slab cannot
|
|
937
|
+
* express. See {@link TabKind} for why each of the four is one of those.
|
|
938
|
+
*
|
|
939
|
+
* ## A LOG AT THE CONTEXT LEVEL, folded like state
|
|
940
|
+
*
|
|
941
|
+
* Unlike {@link ConsoleEvent} these DO fold: the open tab set and the focused
|
|
942
|
+
* tab are what the context IS at `t`. And unlike a frame's tables they are not
|
|
943
|
+
* cleared by a `Snapshot`, because a snapshot is one document committing and a
|
|
944
|
+
* tab surviving a navigation is the ordinary case.
|
|
945
|
+
*
|
|
946
|
+
* A checkpoint therefore RESTATES them, exactly as it restates a frame's asset
|
|
947
|
+
* table: one `Open` per tab still open, carrying its current url and title,
|
|
948
|
+
* plus one `Focus`. Without that a seek onto a checkpoint lands in a context
|
|
949
|
+
* with no tabs in it. A restated `Open` for a tab already open is a no-op and
|
|
950
|
+
* never a reset - the same last-write-wins rule the asset table has.
|
|
951
|
+
*
|
|
952
|
+
* ## No favicon, and that is a limit rather than a deferral
|
|
953
|
+
*
|
|
954
|
+
* `DevToolsAgentHost` exposes `GetTitle` and `GetURL` and nothing else a chip
|
|
955
|
+
* could draw; a favicon lives in `content::FaviconStatus` on the `WebContents`
|
|
956
|
+
* side and no `Outcrawl` command or event carries it. A player labels a tab
|
|
957
|
+
* with its title and the origin of its url. An empty icon slot would be a
|
|
958
|
+
* surface asserting something it does not know.
|
|
959
|
+
*/
|
|
960
|
+
export interface TabEvent extends EventBase {
|
|
961
|
+
type: typeof EventType.Tab;
|
|
962
|
+
/**
|
|
963
|
+
* THE TAB'S IDENTITY: its main-frame ordinal, `tabOf(frame)` tabs in.
|
|
964
|
+
*
|
|
965
|
+
* The same field every other event carries, which is the point - a tab is
|
|
966
|
+
* identified one way in this format and the wire pays for it once. Never
|
|
967
|
+
* reused: the capture layer's tab index only ever increases, so a tab
|
|
968
|
+
* closing and another opening cannot collapse two documents into one id.
|
|
969
|
+
*/
|
|
970
|
+
frame: number;
|
|
971
|
+
kind: TabKind;
|
|
972
|
+
/**
|
|
973
|
+
* The url the browser shows on the tab. Absent means THIS STEP IS NOT ABOUT
|
|
974
|
+
* THE URL, which is not the same as an empty url: a `Close` carries none and
|
|
975
|
+
* must not wipe the label off a chip.
|
|
976
|
+
*/
|
|
977
|
+
url?: string;
|
|
978
|
+
/**
|
|
979
|
+
* The title Blink parsed, not the placeholder Chromium held before the parse.
|
|
980
|
+
* Absent on the same terms as {@link url}.
|
|
981
|
+
*/
|
|
982
|
+
title?: string;
|
|
983
|
+
/**
|
|
984
|
+
* Main-frame ordinal of the tab that SPAWNED this one - a `window.open` or a
|
|
985
|
+
* `target=_blank` link. Absent when nothing spawned it: the session's own
|
|
986
|
+
* first tab, or one the runtime opened on its own behalf.
|
|
987
|
+
*
|
|
988
|
+
* Only ever present on {@link TabKind.Open}, and it is LOAD-BEARING TWICE.
|
|
989
|
+
* A strip draws it, to show that a checkout tab came out of a cart tab. And
|
|
990
|
+
* a player's fold follows it for SUCCESSION: when the focused tab closes and
|
|
991
|
+
* the recording states no new focus, the view moves to the tab that spawned
|
|
992
|
+
* the dead one rather than to the nearest survivor.
|
|
993
|
+
*
|
|
994
|
+
* That second use is why this field must not be pruned as decorative. Both
|
|
995
|
+
* answers look plausible on any single recording - the opener and the nearest
|
|
996
|
+
* survivor are frequently the same tab - so dropping it changes which page a
|
|
997
|
+
* viewer is looking at after a close, silently and only sometimes.
|
|
998
|
+
* `worker/scripts/probe-replay-two-tabs.ts` asserts the value on its
|
|
999
|
+
* page-spawned tab so a regression fails a run instead of changing a replay.
|
|
1000
|
+
*/
|
|
1001
|
+
opener?: number;
|
|
1002
|
+
}
|
|
1003
|
+
export type ReplayEvent = SnapshotEvent | NodeInsertedEvent | NodeRemovedEvent | AttrChangedEvent | TextChangedEvent | StyleSheetAddedEvent | NavigationEvent | InputEvent | ScrollEvent | MouseMoveEvent | ViewportEvent | SubresourceCaptureEvent | RegionFilmEvent | ConsoleEvent | NetworkEvent | TabEvent;
|
|
1004
|
+
export {};
|