effect-browser 0.2.0-beta.9 → 0.3.0-beta.11

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 (205) hide show
  1. package/README.md +861 -1161
  2. package/dist/Browser-B4qncxe7.mjs +628 -0
  3. package/dist/Browser-B4qncxe7.mjs.map +1 -0
  4. package/dist/Browser.d.mts +129 -3
  5. package/dist/Browser.mjs +2 -22
  6. package/dist/BrowserError-Coy7EW1N.d.mts +140 -0
  7. package/dist/BrowserError.d.mts +2 -0
  8. package/dist/BrowserError.mjs +211 -0
  9. package/dist/BrowserError.mjs.map +1 -0
  10. package/dist/BrowserEvent-BRu2juEz.d.mts +279 -0
  11. package/dist/BrowserEvent.d.mts +2 -0
  12. package/dist/BrowserEvent.mjs +297 -0
  13. package/dist/BrowserEvent.mjs.map +1 -0
  14. package/dist/Cdp.d.mts +32 -0
  15. package/dist/Cdp.mjs +61 -0
  16. package/dist/Cdp.mjs.map +1 -0
  17. package/dist/Change-DNvztNLu.mjs +138 -0
  18. package/dist/Change-DNvztNLu.mjs.map +1 -0
  19. package/dist/Change-iMlcuzVD.d.mts +71 -0
  20. package/dist/Change.d.mts +2 -0
  21. package/dist/Change.mjs +2 -0
  22. package/dist/Chromium.d.mts +29 -81
  23. package/dist/Chromium.mjs +57 -392
  24. package/dist/Chromium.mjs.map +1 -1
  25. package/dist/Expect.d.mts +62 -0
  26. package/dist/Expect.mjs +114 -0
  27. package/dist/Expect.mjs.map +1 -0
  28. package/dist/Frame-Di2PxyF7.d.mts +119 -0
  29. package/dist/Frame.d.mts +2 -0
  30. package/dist/Frame.mjs +125 -0
  31. package/dist/Frame.mjs.map +1 -0
  32. package/dist/Moment.d.mts +2 -0
  33. package/dist/Moment.mjs +288 -0
  34. package/dist/Moment.mjs.map +1 -0
  35. package/dist/Motion.d.mts +40 -0
  36. package/dist/Motion.mjs +122 -0
  37. package/dist/Motion.mjs.map +1 -0
  38. package/dist/Page-B5cjaUp5.mjs +618 -0
  39. package/dist/Page-B5cjaUp5.mjs.map +1 -0
  40. package/dist/Page-B7X6DQUn.d.mts +595 -0
  41. package/dist/Page.d.mts +2 -0
  42. package/dist/Page.mjs +2 -0
  43. package/dist/Plan-Akcac63g.d.mts +209 -0
  44. package/dist/Plan-D1wrJVs8.mjs +508 -0
  45. package/dist/Plan-D1wrJVs8.mjs.map +1 -0
  46. package/dist/Plan.d.mts +2 -3
  47. package/dist/Plan.mjs +2 -2
  48. package/dist/Policy.d.mts +76 -0
  49. package/dist/Policy.mjs +237 -0
  50. package/dist/Policy.mjs.map +1 -0
  51. package/dist/Presentation.d.mts +48 -0
  52. package/dist/Presentation.mjs +117 -0
  53. package/dist/Presentation.mjs.map +1 -0
  54. package/dist/Snapshot-B34hZ4Jl.d.mts +68 -0
  55. package/dist/Snapshot.d.mts +2 -0
  56. package/dist/Snapshot.mjs +79 -0
  57. package/dist/Snapshot.mjs.map +1 -0
  58. package/dist/Stage.d.mts +64 -0
  59. package/dist/Stage.mjs +237 -0
  60. package/dist/Stage.mjs.map +1 -0
  61. package/dist/Supervisor-CYWFK3vM.d.mts +280 -0
  62. package/dist/Supervisor-TNKNOVzu.mjs +599 -0
  63. package/dist/Supervisor-TNKNOVzu.mjs.map +1 -0
  64. package/dist/Supervisor.d.mts +2 -0
  65. package/dist/Supervisor.mjs +2 -0
  66. package/dist/context-B6xr8nVi.mjs +261 -0
  67. package/dist/context-B6xr8nVi.mjs.map +1 -0
  68. package/dist/index.d.mts +17 -14
  69. package/dist/index.mjs +18 -13
  70. package/dist/match.inpage-xOe9LNO0.mjs +48 -0
  71. package/dist/match.inpage-xOe9LNO0.mjs.map +1 -0
  72. package/dist/page-BAf__WKX.mjs +5075 -0
  73. package/dist/page-BAf__WKX.mjs.map +1 -0
  74. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  75. package/dist/url-Cd04Okid.mjs +264 -0
  76. package/dist/url-Cd04Okid.mjs.map +1 -0
  77. package/package.json +96 -61
  78. package/src/Browser.ts +672 -0
  79. package/src/BrowserError.ts +234 -0
  80. package/src/BrowserEvent.ts +296 -0
  81. package/src/Cdp.ts +101 -0
  82. package/src/Change.ts +88 -0
  83. package/src/Chromium.ts +102 -0
  84. package/src/Expect.ts +180 -0
  85. package/src/Frame.ts +132 -0
  86. package/src/Moment.ts +479 -0
  87. package/src/Motion.ts +166 -0
  88. package/src/Page.ts +522 -0
  89. package/src/Plan.ts +663 -0
  90. package/src/Policy.ts +325 -0
  91. package/src/Presentation.ts +195 -0
  92. package/src/Snapshot.ts +89 -0
  93. package/src/Stage.ts +467 -0
  94. package/src/Supervisor.ts +599 -0
  95. package/src/index.ts +20 -0
  96. package/src/internal/input/actions.ts +610 -0
  97. package/src/internal/input/dispatch.ts +161 -0
  98. package/src/internal/input/edit.inpage.ts +162 -0
  99. package/src/internal/input/evidence.inpage.ts +164 -0
  100. package/src/internal/input/gate.ts +224 -0
  101. package/src/internal/input/guard.inpage.ts +605 -0
  102. package/src/internal/input/guard.ts +184 -0
  103. package/src/internal/input/keyboard.ts +112 -0
  104. package/src/internal/input/keys.ts +127 -0
  105. package/src/internal/input/perform.ts +376 -0
  106. package/src/internal/input/pointer.ts +153 -0
  107. package/src/internal/input/replies.ts +195 -0
  108. package/src/internal/input/settle.ts +50 -0
  109. package/src/internal/input/style.ts +186 -0
  110. package/src/internal/input/targets.inpage.ts +293 -0
  111. package/src/internal/input/targets.ts +148 -0
  112. package/src/internal/page/bridge.ts +391 -0
  113. package/src/internal/page/context.ts +314 -0
  114. package/src/internal/page/identity.ts +35 -0
  115. package/src/internal/page/lane.ts +311 -0
  116. package/src/internal/page/navigation.ts +183 -0
  117. package/src/internal/page/openings.ts +87 -0
  118. package/src/internal/page/page.ts +165 -0
  119. package/src/internal/page/url.ts +100 -0
  120. package/src/internal/page/viewport.ts +43 -0
  121. package/src/internal/pictures/capture.ts +776 -0
  122. package/src/internal/pictures/clock.ts +141 -0
  123. package/src/internal/pictures/counts.ts +92 -0
  124. package/src/internal/pictures/pictures.ts +521 -0
  125. package/src/internal/pictures/transport.ts +243 -0
  126. package/src/internal/reading/choose.ts +100 -0
  127. package/src/internal/reading/context.inpage.ts +201 -0
  128. package/src/internal/reading/controls.inpage.ts +158 -0
  129. package/src/internal/reading/expect.inpage.ts +70 -0
  130. package/src/internal/reading/match.inpage.ts +92 -0
  131. package/src/internal/reading/names.inpage.ts +306 -0
  132. package/src/internal/reading/outline.inpage.ts +260 -0
  133. package/src/internal/reading/reading.ts +223 -0
  134. package/src/internal/reading/ready.inpage.ts +164 -0
  135. package/src/internal/reading/ready.ts +111 -0
  136. package/src/internal/reading/subjects.inpage.ts +188 -0
  137. package/src/internal/reading/text.inpage.ts +135 -0
  138. package/src/internal/reading/walk.inpage.ts +220 -0
  139. package/src/internal/supervisor/lifecycle.ts +416 -0
  140. package/src/internal/timeline/changes.inpage.ts +216 -0
  141. package/src/internal/timeline/changes.ts +100 -0
  142. package/src/internal/timeline/events.ts +94 -0
  143. package/src/internal/timeline/fold.inpage.ts +123 -0
  144. package/src/internal/timeline/history.inpage.ts +229 -0
  145. package/src/internal/timeline/marks.inpage.ts +207 -0
  146. package/src/internal/timeline/record.inpage.ts +133 -0
  147. package/src/internal/timeline/sight.inpage.ts +122 -0
  148. package/src/internal/timeline/window.ts +96 -0
  149. package/src/internal/timeline/words.ts +65 -0
  150. package/dist/Bootstrap-B9UUXsvF.d.mts +0 -183
  151. package/dist/Bootstrap-CeQOWBy6.mjs +0 -189
  152. package/dist/Bootstrap-CeQOWBy6.mjs.map +0 -1
  153. package/dist/Bootstrap.d.mts +0 -3
  154. package/dist/Bootstrap.mjs +0 -2
  155. package/dist/Browser-BF9_sORP.d.mts +0 -388
  156. package/dist/Browser.mjs.map +0 -1
  157. package/dist/BrowserData-CPIIIHol.d.mts +0 -585
  158. package/dist/BrowserData.d.mts +0 -3
  159. package/dist/BrowserData.mjs +0 -695
  160. package/dist/BrowserData.mjs.map +0 -1
  161. package/dist/BrowserRuntime-B6nT1G4X.d.mts +0 -148
  162. package/dist/BrowserRuntime-C1r2aSRK.mjs +0 -6820
  163. package/dist/BrowserRuntime-C1r2aSRK.mjs.map +0 -1
  164. package/dist/BrowserRuntime.d.mts +0 -3
  165. package/dist/BrowserRuntime.mjs +0 -3
  166. package/dist/Capture.d.mts +0 -56
  167. package/dist/Capture.mjs +0 -678
  168. package/dist/Capture.mjs.map +0 -1
  169. package/dist/CaptureData-CYriZHX1.mjs +0 -201
  170. package/dist/CaptureData-CYriZHX1.mjs.map +0 -1
  171. package/dist/CaptureData.d.mts +0 -219
  172. package/dist/CaptureData.mjs +0 -2
  173. package/dist/Errors.d.mts +0 -158
  174. package/dist/Errors.mjs +0 -207
  175. package/dist/Errors.mjs.map +0 -1
  176. package/dist/Images-yCG9BmRU.mjs +0 -76
  177. package/dist/Images-yCG9BmRU.mjs.map +0 -1
  178. package/dist/OperationOptions-DAeRS6O9.mjs +0 -55
  179. package/dist/OperationOptions-DAeRS6O9.mjs.map +0 -1
  180. package/dist/PageControl.d.mts +0 -15
  181. package/dist/PageControl.mjs +0 -34
  182. package/dist/PageControl.mjs.map +0 -1
  183. package/dist/PageRegistry-B3q9DS3g.mjs +0 -164
  184. package/dist/PageRegistry-B3q9DS3g.mjs.map +0 -1
  185. package/dist/Plan-Dyxu2q8C.mjs +0 -543
  186. package/dist/Plan-Dyxu2q8C.mjs.map +0 -1
  187. package/dist/PlanData-B668RxbQ.d.mts +0 -4831
  188. package/dist/PlanData.d.mts +0 -3
  189. package/dist/PlanData.mjs +0 -268
  190. package/dist/PlanData.mjs.map +0 -1
  191. package/dist/PlanInput-BJcPPncs.mjs +0 -75
  192. package/dist/PlanInput-BJcPPncs.mjs.map +0 -1
  193. package/dist/PublicSession-DCSMMqOj.mjs +0 -5602
  194. package/dist/PublicSession-DCSMMqOj.mjs.map +0 -1
  195. package/dist/Testing.d.mts +0 -479
  196. package/dist/Testing.mjs +0 -2147
  197. package/dist/Testing.mjs.map +0 -1
  198. package/dist/Timeline-fV22Xk-S.d.mts +0 -29
  199. package/dist/Timeline.d.mts +0 -4
  200. package/dist/Timeline.mjs +0 -20
  201. package/dist/Timeline.mjs.map +0 -1
  202. package/dist/TimelineData-BsOn-YGZ.d.mts +0 -3818
  203. package/dist/TimelineData.d.mts +0 -3
  204. package/dist/TimelineData.mjs +0 -482
  205. package/dist/TimelineData.mjs.map +0 -1
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The browser's clock read on the owner's: estimates of their offset from timed probes, how an
3
+ * estimate ages, and the one mapping a browser's pages share. `pictures.ts` takes the probes.
4
+ */
5
+ import { Duration, Effect, Option } from "effect";
6
+
7
+ export interface Probe {
8
+ readonly hostStart: number;
9
+ readonly hostEnd: number;
10
+ readonly browserTime: number;
11
+ }
12
+
13
+ export interface Estimate {
14
+ /** Browser epoch milliseconds minus the owner's host monotonic milliseconds. */
15
+ readonly offsetMillis: number;
16
+ /** Either transport direction can own the entire round trip; symmetry is not assumed. */
17
+ readonly uncertaintyMillis: number;
18
+ readonly roundTripMillis: number;
19
+ readonly sampledAt: number;
20
+ }
21
+
22
+ /** The fastest complete probe limits uncertainty without mistaking transit delay for clock offset. */
23
+ export const estimate = (probes: ReadonlyArray<Probe>): Option.Option<Estimate> => {
24
+ let best: Estimate | undefined;
25
+
26
+ for (const probe of probes) {
27
+ const roundTripMillis = probe.hostEnd - probe.hostStart;
28
+
29
+ if (
30
+ !Number.isFinite(probe.hostStart) ||
31
+ !Number.isFinite(probe.hostEnd) ||
32
+ !Number.isFinite(probe.browserTime) ||
33
+ !Number.isFinite(roundTripMillis) ||
34
+ roundTripMillis < 0
35
+ )
36
+ continue;
37
+ const sampledAt = probe.hostStart + roundTripMillis / 2;
38
+ const offsetMillis = probe.browserTime - sampledAt;
39
+
40
+ if (
41
+ !Number.isFinite(offsetMillis) ||
42
+ !Number.isFinite(probe.browserTime - probe.hostStart) ||
43
+ !Number.isFinite(probe.browserTime - probe.hostEnd)
44
+ )
45
+ continue;
46
+ if (best === undefined || roundTripMillis < best.roundTripMillis)
47
+ best = {
48
+ offsetMillis,
49
+ uncertaintyMillis: roundTripMillis / 2,
50
+ roundTripMillis,
51
+ sampledAt,
52
+ };
53
+ }
54
+
55
+ return Option.fromUndefinedOr(best);
56
+ };
57
+
58
+ // Two machines' clocks drift apart by up to about 100 parts per million, so an estimate says this
59
+ // much less about the offset for each millisecond of its age.
60
+ const driftPerMillis = 1e-4;
61
+
62
+ /** An estimate's uncertainty at `hostTime`, wider the further that is from when it was sampled. */
63
+ export const uncertaintyAt = (estimate: Estimate, hostTime: number): number =>
64
+ estimate.uncertaintyMillis + Math.abs(hostTime - estimate.sampledAt) * driftPerMillis;
65
+
66
+ // Ten seconds widen an estimate by a millisecond, far less than a remote round trip, so it is
67
+ // measured again no more often than this.
68
+ const renewAfter = Duration.seconds(10);
69
+
70
+ /**
71
+ * The browser's one epoch mapping, shared by its pages, input runs and capture generations. It is
72
+ * measured on first need, by the first capture, never ahead of it.
73
+ */
74
+ export interface Mapping {
75
+ /** The latest estimate, without measuring: input uses it, and never waits for one. */
76
+ readonly latest: () => Estimate | undefined;
77
+ /** The latest estimate, measured through `measure` only while the browser has none. */
78
+ readonly current: <E>(measure: Effect.Effect<Estimate, E>) => Effect.Effect<Estimate, E>;
79
+ /**
80
+ * Measure again once ten seconds have passed since the latest measurement began. A failed
81
+ * measurement keeps the estimate, which keeps widening with its age.
82
+ */
83
+ readonly renew: <E>(measure: Effect.Effect<Estimate, E>) => Effect.Effect<void>;
84
+ }
85
+
86
+ /**
87
+ * Whether a new measurement should replace the current estimate. Each interval contains the true
88
+ * offset, so overlapping intervals agree and the narrower one is kept, the current one widened by
89
+ * its age: a probe delayed behind a busy page cannot widen every tab's stamps. A disjoint
90
+ * measurement means the clocks moved (a wall clock step, or drift between a remote browser and
91
+ * this host), so the newer evidence wins.
92
+ */
93
+ export const supersedes = (next: Estimate, current: Estimate): boolean => {
94
+ const aged = uncertaintyAt(current, next.sampledAt);
95
+
96
+ return (
97
+ next.uncertaintyMillis <= aged ||
98
+ Math.abs(next.offsetMillis - current.offsetMillis) > next.uncertaintyMillis + aged
99
+ );
100
+ };
101
+
102
+ /**
103
+ * Every renderer of a browser reads the same host wall clock, so the offset belongs to the
104
+ * browser rather than a page. A busy page cannot fail work that an earlier estimate can serve.
105
+ */
106
+ export const mapping = (now: () => number): Mapping => {
107
+ let latest: Estimate | undefined;
108
+ let measuredAt = Number.NEGATIVE_INFINITY;
109
+
110
+ // Called where a measurement is decided, so no other can be decided before this one begins.
111
+ const adopt = <E>(measure: Effect.Effect<Estimate, E>) => {
112
+ measuredAt = now();
113
+
114
+ return measure.pipe(
115
+ Effect.map((estimate) => {
116
+ if (latest === undefined || supersedes(estimate, latest)) latest = estimate;
117
+
118
+ return latest;
119
+ }),
120
+ );
121
+ };
122
+
123
+ return {
124
+ latest: () => latest,
125
+ current: (measure) =>
126
+ Effect.suspend(() => (latest === undefined ? adopt(measure) : Effect.succeed(latest))),
127
+ renew: (measure) =>
128
+ Effect.suspend(() =>
129
+ latest === undefined || now() - measuredAt < Duration.toMillis(renewAfter)
130
+ ? Effect.void
131
+ : Effect.ignore(adopt(measure)),
132
+ ),
133
+ };
134
+ };
135
+
136
+ export const toHostTime = (calibration: Estimate, browserEpochMillis: number): number =>
137
+ browserEpochMillis - calibration.offsetMillis;
138
+
139
+ /** CDP input timestamps use seconds, unlike browser frame timestamps and the owner's clock. */
140
+ export const toBrowserSeconds = (calibration: Estimate, hostMillis: number): number =>
141
+ (hostMillis + calibration.offsetMillis) / 1000;
@@ -0,0 +1,92 @@
1
+ /** Bounded capture-loss and paint-gap statistics on the owning host clock. */
2
+ import { Duration } from "effect";
3
+
4
+ // Paint-time gaps between consecutive delivered frames.
5
+ const paintGaps = () => {
6
+ let count = 0;
7
+ let totalMillis = 0;
8
+ let minMillis: number | null = null;
9
+ let maxMillis: number | null = null;
10
+ let lastMillis: number | null = null;
11
+
12
+ return {
13
+ add: (gap: number) => {
14
+ count++;
15
+ totalMillis += gap;
16
+ minMillis = minMillis === null ? gap : Math.min(minMillis, gap);
17
+ maxMillis = maxMillis === null ? gap : Math.max(maxMillis, gap);
18
+ lastMillis = gap;
19
+ },
20
+ snapshot: () => ({ count, totalMillis, minMillis, maxMillis, lastMillis }),
21
+ };
22
+ };
23
+
24
+ const none = () => ({
25
+ received: 0,
26
+ accepted: 0,
27
+ late: 0,
28
+ missingTimestamp: 0,
29
+ foreignSize: 0,
30
+ duringPictures: 0,
31
+ lost: 0,
32
+ });
33
+
34
+ type Counted = keyof ReturnType<typeof none>;
35
+
36
+ /** How far back a window of capture counts can reach. */
37
+ export const countsKept = Duration.minutes(1);
38
+
39
+ /**
40
+ * A capture's counts and paint gaps over its page's life, and each with when it happened on the
41
+ * owner's clock for the latest minute, so a window of them can be read.
42
+ */
43
+ export const tally = (now: () => number) => {
44
+ const totals = none();
45
+ const gaps = paintGaps();
46
+
47
+ let recent: Array<{
48
+ readonly at: number;
49
+ readonly counted: Counted | "gap";
50
+ readonly n: number;
51
+ }> = [];
52
+
53
+ let oldest = 0;
54
+
55
+ const note = (counted: Counted | "gap", n: number) => {
56
+ const at = now();
57
+
58
+ recent.push({ at, counted, n });
59
+ while ((recent[oldest]?.at ?? at) < at - Duration.toMillis(countsKept)) oldest++;
60
+ // Shed what is out of reach once it is half the log.
61
+ if (oldest > recent.length / 2) {
62
+ recent = recent.slice(oldest);
63
+ oldest = 0;
64
+ }
65
+ };
66
+
67
+ return {
68
+ count: (counted: Counted, n = 1) => {
69
+ totals[counted] += n;
70
+ note(counted, n);
71
+ },
72
+ gap: (millis: number) => {
73
+ gaps.add(millis);
74
+ note("gap", millis);
75
+ },
76
+ /** Lifetime counts, or those of the latest `windowMillis`, at most a minute. */
77
+ read: (windowMillis?: number) => {
78
+ if (windowMillis === undefined) return { ...totals, gaps: gaps.snapshot() };
79
+ const counts = none();
80
+ const windowed = paintGaps();
81
+ const since = now() - windowMillis;
82
+
83
+ for (const { at, counted, n } of recent.slice(oldest)) {
84
+ if (at < since) continue;
85
+ if (counted === "gap") windowed.add(n);
86
+ else counts[counted] += n;
87
+ }
88
+
89
+ return { ...counts, gaps: windowed.snapshot() };
90
+ },
91
+ };
92
+ };