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.
- package/README.md +861 -1161
- package/dist/Browser-B4qncxe7.mjs +628 -0
- package/dist/Browser-B4qncxe7.mjs.map +1 -0
- package/dist/Browser.d.mts +129 -3
- package/dist/Browser.mjs +2 -22
- package/dist/BrowserError-Coy7EW1N.d.mts +140 -0
- package/dist/BrowserError.d.mts +2 -0
- package/dist/BrowserError.mjs +211 -0
- package/dist/BrowserError.mjs.map +1 -0
- package/dist/BrowserEvent-BRu2juEz.d.mts +279 -0
- package/dist/BrowserEvent.d.mts +2 -0
- package/dist/BrowserEvent.mjs +297 -0
- package/dist/BrowserEvent.mjs.map +1 -0
- package/dist/Cdp.d.mts +32 -0
- package/dist/Cdp.mjs +61 -0
- package/dist/Cdp.mjs.map +1 -0
- package/dist/Change-DNvztNLu.mjs +138 -0
- package/dist/Change-DNvztNLu.mjs.map +1 -0
- package/dist/Change-iMlcuzVD.d.mts +71 -0
- package/dist/Change.d.mts +2 -0
- package/dist/Change.mjs +2 -0
- package/dist/Chromium.d.mts +29 -81
- package/dist/Chromium.mjs +57 -392
- package/dist/Chromium.mjs.map +1 -1
- package/dist/Expect.d.mts +62 -0
- package/dist/Expect.mjs +114 -0
- package/dist/Expect.mjs.map +1 -0
- package/dist/Frame-Di2PxyF7.d.mts +119 -0
- package/dist/Frame.d.mts +2 -0
- package/dist/Frame.mjs +125 -0
- package/dist/Frame.mjs.map +1 -0
- package/dist/Moment.d.mts +2 -0
- package/dist/Moment.mjs +288 -0
- package/dist/Moment.mjs.map +1 -0
- package/dist/Motion.d.mts +40 -0
- package/dist/Motion.mjs +122 -0
- package/dist/Motion.mjs.map +1 -0
- package/dist/Page-B5cjaUp5.mjs +618 -0
- package/dist/Page-B5cjaUp5.mjs.map +1 -0
- package/dist/Page-B7X6DQUn.d.mts +595 -0
- package/dist/Page.d.mts +2 -0
- package/dist/Page.mjs +2 -0
- package/dist/Plan-Akcac63g.d.mts +209 -0
- package/dist/Plan-D1wrJVs8.mjs +508 -0
- package/dist/Plan-D1wrJVs8.mjs.map +1 -0
- package/dist/Plan.d.mts +2 -3
- package/dist/Plan.mjs +2 -2
- package/dist/Policy.d.mts +76 -0
- package/dist/Policy.mjs +237 -0
- package/dist/Policy.mjs.map +1 -0
- package/dist/Presentation.d.mts +48 -0
- package/dist/Presentation.mjs +117 -0
- package/dist/Presentation.mjs.map +1 -0
- package/dist/Snapshot-B34hZ4Jl.d.mts +68 -0
- package/dist/Snapshot.d.mts +2 -0
- package/dist/Snapshot.mjs +79 -0
- package/dist/Snapshot.mjs.map +1 -0
- package/dist/Stage.d.mts +64 -0
- package/dist/Stage.mjs +237 -0
- package/dist/Stage.mjs.map +1 -0
- package/dist/Supervisor-CYWFK3vM.d.mts +280 -0
- package/dist/Supervisor-TNKNOVzu.mjs +599 -0
- package/dist/Supervisor-TNKNOVzu.mjs.map +1 -0
- package/dist/Supervisor.d.mts +2 -0
- package/dist/Supervisor.mjs +2 -0
- package/dist/context-B6xr8nVi.mjs +261 -0
- package/dist/context-B6xr8nVi.mjs.map +1 -0
- package/dist/index.d.mts +17 -14
- package/dist/index.mjs +18 -13
- package/dist/match.inpage-xOe9LNO0.mjs +48 -0
- package/dist/match.inpage-xOe9LNO0.mjs.map +1 -0
- package/dist/page-BAf__WKX.mjs +5075 -0
- package/dist/page-BAf__WKX.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/url-Cd04Okid.mjs +264 -0
- package/dist/url-Cd04Okid.mjs.map +1 -0
- package/package.json +96 -61
- package/src/Browser.ts +672 -0
- package/src/BrowserError.ts +234 -0
- package/src/BrowserEvent.ts +296 -0
- package/src/Cdp.ts +101 -0
- package/src/Change.ts +88 -0
- package/src/Chromium.ts +102 -0
- package/src/Expect.ts +180 -0
- package/src/Frame.ts +132 -0
- package/src/Moment.ts +479 -0
- package/src/Motion.ts +166 -0
- package/src/Page.ts +522 -0
- package/src/Plan.ts +663 -0
- package/src/Policy.ts +325 -0
- package/src/Presentation.ts +195 -0
- package/src/Snapshot.ts +89 -0
- package/src/Stage.ts +467 -0
- package/src/Supervisor.ts +599 -0
- package/src/index.ts +20 -0
- package/src/internal/input/actions.ts +610 -0
- package/src/internal/input/dispatch.ts +161 -0
- package/src/internal/input/edit.inpage.ts +162 -0
- package/src/internal/input/evidence.inpage.ts +164 -0
- package/src/internal/input/gate.ts +224 -0
- package/src/internal/input/guard.inpage.ts +605 -0
- package/src/internal/input/guard.ts +184 -0
- package/src/internal/input/keyboard.ts +112 -0
- package/src/internal/input/keys.ts +127 -0
- package/src/internal/input/perform.ts +376 -0
- package/src/internal/input/pointer.ts +153 -0
- package/src/internal/input/replies.ts +195 -0
- package/src/internal/input/settle.ts +50 -0
- package/src/internal/input/style.ts +186 -0
- package/src/internal/input/targets.inpage.ts +293 -0
- package/src/internal/input/targets.ts +148 -0
- package/src/internal/page/bridge.ts +391 -0
- package/src/internal/page/context.ts +314 -0
- package/src/internal/page/identity.ts +35 -0
- package/src/internal/page/lane.ts +311 -0
- package/src/internal/page/navigation.ts +183 -0
- package/src/internal/page/openings.ts +87 -0
- package/src/internal/page/page.ts +165 -0
- package/src/internal/page/url.ts +100 -0
- package/src/internal/page/viewport.ts +43 -0
- package/src/internal/pictures/capture.ts +776 -0
- package/src/internal/pictures/clock.ts +141 -0
- package/src/internal/pictures/counts.ts +92 -0
- package/src/internal/pictures/pictures.ts +521 -0
- package/src/internal/pictures/transport.ts +243 -0
- package/src/internal/reading/choose.ts +100 -0
- package/src/internal/reading/context.inpage.ts +201 -0
- package/src/internal/reading/controls.inpage.ts +158 -0
- package/src/internal/reading/expect.inpage.ts +70 -0
- package/src/internal/reading/match.inpage.ts +92 -0
- package/src/internal/reading/names.inpage.ts +306 -0
- package/src/internal/reading/outline.inpage.ts +260 -0
- package/src/internal/reading/reading.ts +223 -0
- package/src/internal/reading/ready.inpage.ts +164 -0
- package/src/internal/reading/ready.ts +111 -0
- package/src/internal/reading/subjects.inpage.ts +188 -0
- package/src/internal/reading/text.inpage.ts +135 -0
- package/src/internal/reading/walk.inpage.ts +220 -0
- package/src/internal/supervisor/lifecycle.ts +416 -0
- package/src/internal/timeline/changes.inpage.ts +216 -0
- package/src/internal/timeline/changes.ts +100 -0
- package/src/internal/timeline/events.ts +94 -0
- package/src/internal/timeline/fold.inpage.ts +123 -0
- package/src/internal/timeline/history.inpage.ts +229 -0
- package/src/internal/timeline/marks.inpage.ts +207 -0
- package/src/internal/timeline/record.inpage.ts +133 -0
- package/src/internal/timeline/sight.inpage.ts +122 -0
- package/src/internal/timeline/window.ts +96 -0
- package/src/internal/timeline/words.ts +65 -0
- package/dist/Bootstrap-B9UUXsvF.d.mts +0 -183
- package/dist/Bootstrap-CeQOWBy6.mjs +0 -189
- package/dist/Bootstrap-CeQOWBy6.mjs.map +0 -1
- package/dist/Bootstrap.d.mts +0 -3
- package/dist/Bootstrap.mjs +0 -2
- package/dist/Browser-BF9_sORP.d.mts +0 -388
- package/dist/Browser.mjs.map +0 -1
- package/dist/BrowserData-CPIIIHol.d.mts +0 -585
- package/dist/BrowserData.d.mts +0 -3
- package/dist/BrowserData.mjs +0 -695
- package/dist/BrowserData.mjs.map +0 -1
- package/dist/BrowserRuntime-B6nT1G4X.d.mts +0 -148
- package/dist/BrowserRuntime-C1r2aSRK.mjs +0 -6820
- package/dist/BrowserRuntime-C1r2aSRK.mjs.map +0 -1
- package/dist/BrowserRuntime.d.mts +0 -3
- package/dist/BrowserRuntime.mjs +0 -3
- package/dist/Capture.d.mts +0 -56
- package/dist/Capture.mjs +0 -678
- package/dist/Capture.mjs.map +0 -1
- package/dist/CaptureData-CYriZHX1.mjs +0 -201
- package/dist/CaptureData-CYriZHX1.mjs.map +0 -1
- package/dist/CaptureData.d.mts +0 -219
- package/dist/CaptureData.mjs +0 -2
- package/dist/Errors.d.mts +0 -158
- package/dist/Errors.mjs +0 -207
- package/dist/Errors.mjs.map +0 -1
- package/dist/Images-yCG9BmRU.mjs +0 -76
- package/dist/Images-yCG9BmRU.mjs.map +0 -1
- package/dist/OperationOptions-DAeRS6O9.mjs +0 -55
- package/dist/OperationOptions-DAeRS6O9.mjs.map +0 -1
- package/dist/PageControl.d.mts +0 -15
- package/dist/PageControl.mjs +0 -34
- package/dist/PageControl.mjs.map +0 -1
- package/dist/PageRegistry-B3q9DS3g.mjs +0 -164
- package/dist/PageRegistry-B3q9DS3g.mjs.map +0 -1
- package/dist/Plan-Dyxu2q8C.mjs +0 -543
- package/dist/Plan-Dyxu2q8C.mjs.map +0 -1
- package/dist/PlanData-B668RxbQ.d.mts +0 -4831
- package/dist/PlanData.d.mts +0 -3
- package/dist/PlanData.mjs +0 -268
- package/dist/PlanData.mjs.map +0 -1
- package/dist/PlanInput-BJcPPncs.mjs +0 -75
- package/dist/PlanInput-BJcPPncs.mjs.map +0 -1
- package/dist/PublicSession-DCSMMqOj.mjs +0 -5602
- package/dist/PublicSession-DCSMMqOj.mjs.map +0 -1
- package/dist/Testing.d.mts +0 -479
- package/dist/Testing.mjs +0 -2147
- package/dist/Testing.mjs.map +0 -1
- package/dist/Timeline-fV22Xk-S.d.mts +0 -29
- package/dist/Timeline.d.mts +0 -4
- package/dist/Timeline.mjs +0 -20
- package/dist/Timeline.mjs.map +0 -1
- package/dist/TimelineData-BsOn-YGZ.d.mts +0 -3818
- package/dist/TimelineData.d.mts +0 -3
- package/dist/TimelineData.mjs +0 -482
- package/dist/TimelineData.mjs.map +0 -1
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bridge to the page script, and the page's documents as its own session sees them commit and
|
|
3
|
+
* load, so a load follows its commit. At the library's first read of a page, its own protocol
|
|
4
|
+
* session registers the script, so every later document runs it in an isolated world from its
|
|
5
|
+
* start; calls go to the current document's world. The script is composed from the domains'
|
|
6
|
+
* page-side parts, the `*.inpage.ts` modules, in the order they depend on one another. At the first
|
|
7
|
+
* read of a page's changes, its session registers the recorder too, so every later document
|
|
8
|
+
* records from its start.
|
|
9
|
+
*/
|
|
10
|
+
import { Effect, Schema, SynchronizedRef } from "effect";
|
|
11
|
+
|
|
12
|
+
import { type BrowserError, Failed } from "../../BrowserError.ts";
|
|
13
|
+
import { Navigated, PageLoaded } from "../../BrowserEvent.ts";
|
|
14
|
+
import { edit, type Edit } from "../input/edit.inpage.ts";
|
|
15
|
+
import { evidence } from "../input/evidence.inpage.ts";
|
|
16
|
+
import { guard, type Guard } from "../input/guard.inpage.ts";
|
|
17
|
+
import { targets, type Targets } from "../input/targets.inpage.ts";
|
|
18
|
+
import { context } from "../reading/context.inpage.ts";
|
|
19
|
+
import { controls, type Controls } from "../reading/controls.inpage.ts";
|
|
20
|
+
import { expectations, type Expectations } from "../reading/expect.inpage.ts";
|
|
21
|
+
import { match } from "../reading/match.inpage.ts";
|
|
22
|
+
import { names } from "../reading/names.inpage.ts";
|
|
23
|
+
import { outline, type Outline } from "../reading/outline.inpage.ts";
|
|
24
|
+
import { ready } from "../reading/ready.inpage.ts";
|
|
25
|
+
import { subjects, type Subjects } from "../reading/subjects.inpage.ts";
|
|
26
|
+
import { text, type Texts } from "../reading/text.inpage.ts";
|
|
27
|
+
import { walk } from "../reading/walk.inpage.ts";
|
|
28
|
+
import { changes, type Changes } from "../timeline/changes.inpage.ts";
|
|
29
|
+
import { fold } from "../timeline/fold.inpage.ts";
|
|
30
|
+
import { history } from "../timeline/history.inpage.ts";
|
|
31
|
+
import { marks } from "../timeline/marks.inpage.ts";
|
|
32
|
+
import { record, type Recorder } from "../timeline/record.inpage.ts";
|
|
33
|
+
import { sight } from "../timeline/sight.inpage.ts";
|
|
34
|
+
import { contextGone, failWith, type PageContext, undispatched } from "./context.ts";
|
|
35
|
+
import * as Url from "./url.ts";
|
|
36
|
+
|
|
37
|
+
/** What the script installs: a version, and a function of one of its parts for each call. */
|
|
38
|
+
export interface PageApi {
|
|
39
|
+
readonly version: number;
|
|
40
|
+
snapshot: Outline["snapshot"];
|
|
41
|
+
find: Subjects["find"];
|
|
42
|
+
until: Expectations["until"];
|
|
43
|
+
waitUntil: Controls["waitUntil"];
|
|
44
|
+
text: Texts["read"];
|
|
45
|
+
record: Recorder["start"];
|
|
46
|
+
changes: Changes["read"];
|
|
47
|
+
ready: ReturnType<typeof ready>["wait"];
|
|
48
|
+
settle: ReturnType<typeof ready>["settle"];
|
|
49
|
+
point: Targets["point"];
|
|
50
|
+
scrollPlan: Targets["scrollPlan"];
|
|
51
|
+
viewport: Outline["viewport"];
|
|
52
|
+
/** The screen as the page reads it, which another session may emulate. */
|
|
53
|
+
screenSize: () => { readonly width: number; readonly height: number };
|
|
54
|
+
prepareInput: Guard["prepareInput"];
|
|
55
|
+
validateInput: Guard["validateInput"];
|
|
56
|
+
typeable: Edit["typeable"];
|
|
57
|
+
focus: Edit["focus"];
|
|
58
|
+
focusOn: Edit["focusOn"];
|
|
59
|
+
select: Edit["select"];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare global {
|
|
63
|
+
// The API this script installs, which exists only inside the library's isolated world.
|
|
64
|
+
var __effectBrowser: PageApi | undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Like the parts it receives, this is evaluated from its source text.
|
|
68
|
+
const install = (
|
|
69
|
+
makeNames: typeof names,
|
|
70
|
+
makeWalk: typeof walk,
|
|
71
|
+
makeMatch: typeof match,
|
|
72
|
+
makeContext: typeof context,
|
|
73
|
+
makeSubjects: typeof subjects,
|
|
74
|
+
makeExpectations: typeof expectations,
|
|
75
|
+
makeText: typeof text,
|
|
76
|
+
makeControls: typeof controls,
|
|
77
|
+
makeFold: typeof fold,
|
|
78
|
+
makeHistory: typeof history,
|
|
79
|
+
makeSight: typeof sight,
|
|
80
|
+
makeMarks: typeof marks,
|
|
81
|
+
makeRecord: typeof record,
|
|
82
|
+
makeChanges: typeof changes,
|
|
83
|
+
makeReady: typeof ready,
|
|
84
|
+
makeOutline: typeof outline,
|
|
85
|
+
makeTargets: typeof targets,
|
|
86
|
+
makeEvidence: typeof evidence,
|
|
87
|
+
makeGuard: typeof guard,
|
|
88
|
+
makeEdit: typeof edit,
|
|
89
|
+
makeAddresses: typeof Url.addresses,
|
|
90
|
+
): PageApi => {
|
|
91
|
+
const installed = globalThis.__effectBrowser;
|
|
92
|
+
|
|
93
|
+
if (installed !== undefined && installed.version === 19) return installed;
|
|
94
|
+
const named = makeNames();
|
|
95
|
+
const walked = makeWalk(named);
|
|
96
|
+
const placing = makeContext(named, walked);
|
|
97
|
+
const subjected = makeSubjects(named, walked, makeMatch(), placing);
|
|
98
|
+
const texts = makeText(named, walked);
|
|
99
|
+
const controlled = makeControls(named, walked, subjected, texts);
|
|
100
|
+
const kept = makeHistory(makeFold());
|
|
101
|
+
const seeing = makeSight(named, walked, texts, kept);
|
|
102
|
+
const marking = makeMarks(named, kept, seeing);
|
|
103
|
+
const recorder = makeRecord(named, texts, kept, seeing, marking);
|
|
104
|
+
const read = makeOutline(named, walked, subjected, texts, controlled, makeAddresses());
|
|
105
|
+
const located = makeTargets(named, walked, placing);
|
|
106
|
+
const guarded = makeGuard(named, located, makeEvidence(named, placing));
|
|
107
|
+
const edited = makeEdit(named, guarded, placing);
|
|
108
|
+
const readiness = makeReady(walked, texts, kept, recorder);
|
|
109
|
+
|
|
110
|
+
const api: PageApi = {
|
|
111
|
+
version: 19,
|
|
112
|
+
snapshot: read.snapshot,
|
|
113
|
+
find: subjected.find,
|
|
114
|
+
until: makeExpectations(named, subjected).until,
|
|
115
|
+
waitUntil: controlled.waitUntil,
|
|
116
|
+
text: texts.read,
|
|
117
|
+
record: recorder.start,
|
|
118
|
+
changes: makeChanges(named, placing, kept, seeing, marking, recorder).read,
|
|
119
|
+
ready: readiness.wait,
|
|
120
|
+
settle: readiness.settle,
|
|
121
|
+
point: located.point,
|
|
122
|
+
scrollPlan: located.scrollPlan,
|
|
123
|
+
viewport: read.viewport,
|
|
124
|
+
screenSize: () => ({ width: screen.width, height: screen.height }),
|
|
125
|
+
prepareInput: guarded.prepareInput,
|
|
126
|
+
validateInput: guarded.validateInput,
|
|
127
|
+
typeable: edited.typeable,
|
|
128
|
+
focus: edited.focus,
|
|
129
|
+
focusOn: edited.focusOn,
|
|
130
|
+
select: edited.select,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
globalThis.__effectBrowser = api;
|
|
134
|
+
|
|
135
|
+
return api;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** The expression that installs the script and evaluates to its API. */
|
|
139
|
+
export const installSource = `(${install.toString()})(${[names, walk, match, context, subjects, expectations, text, controls, fold, history, sight, marks, record, changes, ready, outline, targets, evidence, guard, edit, Url.addresses].join(", ")})`;
|
|
140
|
+
|
|
141
|
+
const worldName = "effect-browser";
|
|
142
|
+
|
|
143
|
+
// What starts the recorder in each new document, after the script installed itself there.
|
|
144
|
+
const recorderSource = `if (globalThis === globalThis.top) globalThis.__effectBrowser?.record();`;
|
|
145
|
+
|
|
146
|
+
// What a call reads in a world without the script's API. A context id can name another world once
|
|
147
|
+
// the page has moved to a new process, so a cached id that reads this is not the library's world.
|
|
148
|
+
const missing = "effect-browser: no __effectBrowser in this world";
|
|
149
|
+
|
|
150
|
+
const isMissing = (error: BrowserError) =>
|
|
151
|
+
error.reason._tag === "Failed" && error.reason.detail === missing;
|
|
152
|
+
|
|
153
|
+
/** A frame as a page session reports its commit. */
|
|
154
|
+
export const Commit = Schema.Struct({
|
|
155
|
+
parentId: Schema.optionalKey(Schema.String),
|
|
156
|
+
loaderId: Schema.String,
|
|
157
|
+
url: Schema.String,
|
|
158
|
+
urlFragment: Schema.optionalKey(Schema.String),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/** `Page.frameNavigated` or `Page.navigatedWithinDocument`, as far as `mainFrame` reads them. */
|
|
162
|
+
export const MainFrameEvent = Schema.Union([
|
|
163
|
+
Schema.Struct({ frame: Commit }),
|
|
164
|
+
Schema.Struct({ frameId: Schema.String, url: Schema.String }),
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
/** A commit's loader and its address, redacted. */
|
|
168
|
+
export const commitOf = ({ loaderId, url, urlFragment = "" }: typeof Commit.Type) => ({
|
|
169
|
+
loader: loaderId,
|
|
170
|
+
url: Url.redact(url + urlFragment),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* What a page session's commit or move says of `page`'s main frame: a new document's `loader` and
|
|
175
|
+
* its address, or a move's address; nothing for another frame. Only a commit is a new document:
|
|
176
|
+
* `frameStartedLoading` also fires on `pushState`, a move within the document.
|
|
177
|
+
*/
|
|
178
|
+
export const mainFrame = (page: string, event: typeof MainFrameEvent.Type) =>
|
|
179
|
+
"frame" in event
|
|
180
|
+
? event.frame.parentId === undefined
|
|
181
|
+
? commitOf(event.frame)
|
|
182
|
+
: undefined
|
|
183
|
+
: event.frameId === page
|
|
184
|
+
? { loader: undefined, url: Url.redact(event.url) }
|
|
185
|
+
: undefined;
|
|
186
|
+
|
|
187
|
+
/** A call to the page script, its arguments quoted as JavaScript literals. */
|
|
188
|
+
export const scriptCall = (name: string, ...args: ReadonlyArray<unknown>): string =>
|
|
189
|
+
`${name}(${args.map((arg) => JSON.stringify(arg)).join(", ")})`;
|
|
190
|
+
|
|
191
|
+
export const make = Effect.fnUntraced(function* (page: PageContext) {
|
|
192
|
+
const { id, cdp, native, span, publish, now } = page;
|
|
193
|
+
const { send } = page.protocol;
|
|
194
|
+
// How far this session has registered: the script, then the recorder, which the first read of
|
|
195
|
+
// changes asks for. One caller takes each step, once, while the others wait.
|
|
196
|
+
const registration = yield* SynchronizedRef.make<"none" | "script" | "recorder">("none");
|
|
197
|
+
// The current document's world, forgotten when the main frame commits another document; the
|
|
198
|
+
// commits this session has seen, the latest marked on the page; the page's address; and how far
|
|
199
|
+
// the current document has loaded.
|
|
200
|
+
let world: number | undefined;
|
|
201
|
+
let documents = 0;
|
|
202
|
+
let url = Url.redact(page.url);
|
|
203
|
+
let loaded: PageLoaded | undefined;
|
|
204
|
+
// The latest commits' loaders and the documents they began, newest last: a capture connection
|
|
205
|
+
// sees the same commits, and numbers its frames by them.
|
|
206
|
+
const loaders = new Map<string, number>();
|
|
207
|
+
|
|
208
|
+
const onMainFrame = (event: typeof MainFrameEvent.Type) => {
|
|
209
|
+
const seen = mainFrame(id, event);
|
|
210
|
+
|
|
211
|
+
if (seen === undefined) return;
|
|
212
|
+
if (seen.loader !== undefined) {
|
|
213
|
+
documents++;
|
|
214
|
+
loaders.delete(seen.loader);
|
|
215
|
+
loaders.set(seen.loader, documents);
|
|
216
|
+
const [oldest] = loaders.keys();
|
|
217
|
+
|
|
218
|
+
if (loaders.size > 8 && oldest !== undefined) loaders.delete(oldest);
|
|
219
|
+
page.activity.documentAt = now();
|
|
220
|
+
world = undefined;
|
|
221
|
+
loaded = undefined;
|
|
222
|
+
}
|
|
223
|
+
url = seen.url;
|
|
224
|
+
const sameDocument = seen.loader === undefined;
|
|
225
|
+
|
|
226
|
+
publish(new Navigated({ at: now(), page: id, url, document: documents, sameDocument }));
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// The session tells these of the main frame alone.
|
|
230
|
+
const onLoaded = (state: PageLoaded["state"]) => () => {
|
|
231
|
+
loaded = new PageLoaded({ at: now(), page: id, state });
|
|
232
|
+
publish(loaded);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
cdp.on("Page.frameNavigated", onMainFrame);
|
|
236
|
+
cdp.on("Page.navigatedWithinDocument", onMainFrame);
|
|
237
|
+
cdp.on("Page.domContentEventFired", onLoaded("domcontentloaded"));
|
|
238
|
+
cdp.on("Page.loadEventFired", onLoaded("load"));
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The main-frame commits seen so far, once the Page domain, which registration turned on, has
|
|
242
|
+
* answered, so that every later one counts. A commit within about one round trip of the count
|
|
243
|
+
* can still be missed.
|
|
244
|
+
*/
|
|
245
|
+
const currentDocument = (operation: string) =>
|
|
246
|
+
page.paging(operation).pipe(Effect.map(() => documents));
|
|
247
|
+
|
|
248
|
+
/** What a frame that arrives now shows: its session, the page's current document and address. */
|
|
249
|
+
const frameTag = () => ({
|
|
250
|
+
session: page.session,
|
|
251
|
+
connection: page.connection,
|
|
252
|
+
document: documents,
|
|
253
|
+
url,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/** The document the main frame's latest commit of `loader` began, if this session counted it. */
|
|
257
|
+
const documentOf = (loader: string) => loaders.get(loader);
|
|
258
|
+
|
|
259
|
+
// The registration belongs to this session and the world to the page, so a session that attaches
|
|
260
|
+
// later, as after a reconnect, registers again and finds the script already installed. With the
|
|
261
|
+
// Page domain enabled, a source runs in each new document, in the order registered;
|
|
262
|
+
// `runImmediately` runs it now.
|
|
263
|
+
const addScript = (operation: string, source: string) =>
|
|
264
|
+
native(operation, () =>
|
|
265
|
+
send("Page.addScriptToEvaluateOnNewDocument", { source, worldName, runImmediately: true }),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const register = (operation: string) =>
|
|
269
|
+
Effect.all(
|
|
270
|
+
[
|
|
271
|
+
page.paging(operation),
|
|
272
|
+
addScript(operation, `if (globalThis === globalThis.top) ${installSource};`),
|
|
273
|
+
],
|
|
274
|
+
{ concurrency: "unbounded" },
|
|
275
|
+
).pipe(span("Page.register", {}, "Debug"));
|
|
276
|
+
|
|
277
|
+
/** Take registration from `from` to `to` by `step`, unless it is past `from` already. */
|
|
278
|
+
const advance = (
|
|
279
|
+
from: "none" | "script",
|
|
280
|
+
to: "script" | "recorder",
|
|
281
|
+
step: Effect.Effect<unknown, BrowserError>,
|
|
282
|
+
) =>
|
|
283
|
+
SynchronizedRef.updateEffect(registration, (state) =>
|
|
284
|
+
state === from ? Effect.as(step, to) : Effect.succeed(state),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
/** Register the script once, and the recorder after it once a read of changes asks for it. */
|
|
288
|
+
const ensure = (operation: string, recorder: boolean) =>
|
|
289
|
+
Effect.andThen(
|
|
290
|
+
advance("none", "script", register(operation)),
|
|
291
|
+
recorder ? advance("script", "recorder", addScript(operation, recorderSource)) : Effect.void,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const createWorld = (operation: string) =>
|
|
295
|
+
Effect.gen(function* () {
|
|
296
|
+
yield* ensure(operation, false);
|
|
297
|
+
const document = documents;
|
|
298
|
+
|
|
299
|
+
// The main frame's id is the page's target id.
|
|
300
|
+
const created = yield* native(operation, () =>
|
|
301
|
+
send("Page.createIsolatedWorld", { frameId: id, worldName }),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// A world the page replaced while it was being looked up serves this call, not later ones.
|
|
305
|
+
if (document === documents) world = created.executionContextId;
|
|
306
|
+
|
|
307
|
+
return created.executionContextId;
|
|
308
|
+
}).pipe(span("Page.createWorld", {}, "Debug"));
|
|
309
|
+
|
|
310
|
+
/** The current document's world, created if it has none yet. */
|
|
311
|
+
const current = (operation: string) =>
|
|
312
|
+
Effect.suspend(() => (world === undefined ? createWorld(operation) : Effect.succeed(world)));
|
|
313
|
+
|
|
314
|
+
/** A world of the current document without the page script, as the clock probe needs. */
|
|
315
|
+
const bareWorld = (operation: string) =>
|
|
316
|
+
native(operation, () =>
|
|
317
|
+
send("Page.createIsolatedWorld", { frameId: id, worldName: `${worldName}-clock` }),
|
|
318
|
+
).pipe(Effect.map((created) => created.executionContextId));
|
|
319
|
+
|
|
320
|
+
const run = (operation: string, expression: string, contextId: number) =>
|
|
321
|
+
native(operation, () =>
|
|
322
|
+
send("Runtime.evaluate", { contextId, expression, returnByValue: true, awaitPromise: true }),
|
|
323
|
+
).pipe(
|
|
324
|
+
Effect.flatMap(({ result, exceptionDetails: thrown }) =>
|
|
325
|
+
thrown === undefined
|
|
326
|
+
? Effect.succeed<unknown>(result.value)
|
|
327
|
+
: failWith(
|
|
328
|
+
operation,
|
|
329
|
+
new Failed({ detail: thrown.exception?.description ?? thrown.text }),
|
|
330
|
+
),
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const evaluateIn = (operation: string, call: string, contextId: number) =>
|
|
335
|
+
run(
|
|
336
|
+
operation,
|
|
337
|
+
`globalThis.__effectBrowser === undefined ? ${JSON.stringify(missing)} : globalThis.__effectBrowser.${call}`,
|
|
338
|
+
contextId,
|
|
339
|
+
).pipe(
|
|
340
|
+
Effect.filterOrFail(
|
|
341
|
+
(value) => value !== missing,
|
|
342
|
+
() => undispatched(operation, new Failed({ detail: missing })),
|
|
343
|
+
),
|
|
344
|
+
// One round trip to the page. Its arguments can hold typed text, so only the call is named.
|
|
345
|
+
span("Page.evaluate", { function: call.split("(")[0] }, "Trace"),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// Ordinary reads can recreate a document's world. Approval validation deliberately cannot. A
|
|
349
|
+
// world found by its name that lacks the script, in a document the registration missed, gets it.
|
|
350
|
+
const evaluateWithContext = (operation: string, call: string) => {
|
|
351
|
+
const attempt = (contextId: number) =>
|
|
352
|
+
evaluateIn(operation, call, contextId).pipe(Effect.map((value) => ({ contextId, value })));
|
|
353
|
+
|
|
354
|
+
const again = createWorld(operation).pipe(
|
|
355
|
+
Effect.flatMap((contextId) =>
|
|
356
|
+
attempt(contextId).pipe(
|
|
357
|
+
Effect.catchIf(isMissing, () =>
|
|
358
|
+
run(operation, `${installSource}.version`, contextId).pipe(
|
|
359
|
+
Effect.andThen(attempt(contextId)),
|
|
360
|
+
),
|
|
361
|
+
),
|
|
362
|
+
),
|
|
363
|
+
),
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
return current(operation).pipe(
|
|
367
|
+
Effect.flatMap(attempt),
|
|
368
|
+
Effect.catchIf(contextGone, () => again),
|
|
369
|
+
);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const evaluate = (operation: string, call: string) =>
|
|
373
|
+
evaluateWithContext(operation, call).pipe(Effect.map(({ value }) => value));
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
current,
|
|
377
|
+
bareWorld,
|
|
378
|
+
evaluate,
|
|
379
|
+
evaluateIn,
|
|
380
|
+
evaluateWithContext,
|
|
381
|
+
currentDocument,
|
|
382
|
+
frameTag,
|
|
383
|
+
documentOf,
|
|
384
|
+
/** How far the current document has loaded, as this session heard; nothing until parsed. */
|
|
385
|
+
loaded: () => loaded,
|
|
386
|
+
/** Register the recorder, so every later document records from its start. */
|
|
387
|
+
registerRecorder: (operation: string) => ensure(operation, true),
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
export type Bridge = Effect.Success<ReturnType<typeof make>>;
|