effect-browser 0.3.0-beta.14 → 0.3.0-beta.16
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 +24 -11
- package/dist/{Browser-BBSySyjT.mjs → Browser-DRPthkNO.mjs} +4 -4
- package/dist/Browser-DRPthkNO.mjs.map +1 -0
- package/dist/Browser.d.mts +2 -2
- package/dist/Browser.mjs +1 -1
- package/dist/{BrowserEvent-BRu2juEz.d.mts → BrowserEvent-dq6FRT8w.d.mts} +9 -9
- package/dist/BrowserEvent.d.mts +1 -1
- package/dist/BrowserEvent.mjs +7 -12
- package/dist/BrowserEvent.mjs.map +1 -1
- package/dist/Cdp.mjs +2 -2
- package/dist/{Change-iMlcuzVD.d.mts → Change-DqING3tr.d.mts} +2 -2
- package/dist/Change.d.mts +1 -1
- package/dist/Chromium.mjs +2 -2
- package/dist/Expect.d.mts +1 -1
- package/dist/Expect.mjs +2 -2
- package/dist/Moment.d.mts +1 -1
- package/dist/Moment.mjs +1 -1
- package/dist/Motion-DND6I-64.d.mts +40 -0
- package/dist/Motion.d.mts +2 -40
- package/dist/Motion.mjs +1 -2
- package/dist/Motion.mjs.map +1 -1
- package/dist/{Page-B5cjaUp5.mjs → Page-C_VkBVwI.mjs} +5 -9
- package/dist/Page-C_VkBVwI.mjs.map +1 -0
- package/dist/{Page-DL9eylp3.d.mts → Page-jHmSJeOS.d.mts} +6 -13
- package/dist/Page.d.mts +1 -1
- package/dist/Page.mjs +1 -1
- package/dist/{Plan-6As6vcwT.d.mts → Plan-DLtRksuu.d.mts} +3 -3
- package/dist/{Plan-CXA2BW-G.mjs → Plan-X-JkACAB.mjs} +6 -9
- package/dist/Plan-X-JkACAB.mjs.map +1 -0
- package/dist/Plan.d.mts +1 -1
- package/dist/Plan.mjs +1 -1
- package/dist/Policy.d.mts +1 -1
- package/dist/Policy.mjs +1 -1
- package/dist/Presentation.d.mts +2 -2
- package/dist/Presentation.mjs +3 -2
- package/dist/Presentation.mjs.map +1 -1
- package/dist/Stage.d.mts +1 -1
- package/dist/Stage.mjs +3 -3
- package/dist/Stage.mjs.map +1 -1
- package/dist/{Supervisor-BE2sGHBK.mjs → Supervisor-BgWP8o9t.mjs} +116 -66
- package/dist/Supervisor-BgWP8o9t.mjs.map +1 -0
- package/dist/{Supervisor-W60pZWHh.d.mts → Supervisor-mVPeEUp8.d.mts} +33 -12
- package/dist/Supervisor.d.mts +2 -2
- package/dist/Supervisor.mjs +2 -2
- package/dist/{context-BAjRj7Xo.mjs → context-rYTk-3Cv.mjs} +68 -3
- package/dist/context-rYTk-3Cv.mjs.map +1 -0
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +4 -4
- package/dist/match.inpage-xOe9LNO0.mjs.map +1 -1
- package/dist/{page-B92QnRfT.mjs → page-ClEMFnBG.mjs} +48 -110
- package/dist/page-ClEMFnBG.mjs.map +1 -0
- package/package.json +1 -1
- package/src/Browser.ts +1 -1
- package/src/BrowserEvent.ts +7 -12
- package/src/Motion.ts +1 -2
- package/src/Page.ts +5 -9
- package/src/Plan.ts +1 -2
- package/src/Presentation.ts +2 -1
- package/src/Stage.ts +2 -1
- package/src/Supervisor.ts +168 -166
- package/src/internal/input/actions.ts +25 -33
- package/src/internal/input/dispatch.ts +3 -4
- package/src/internal/input/edit.inpage.ts +3 -9
- package/src/internal/input/evidence.inpage.ts +2 -20
- package/src/internal/input/guard.inpage.ts +9 -29
- package/src/internal/input/guard.ts +12 -1
- package/src/internal/input/targets.inpage.ts +4 -8
- package/src/internal/input/targets.ts +8 -11
- package/src/internal/page/context.ts +2 -2
- package/src/internal/pictures/capture.ts +4 -4
- package/src/internal/pictures/clock.ts +8 -3
- package/src/internal/pictures/pictures.ts +2 -1
- package/src/internal/reading/controls.inpage.ts +8 -1
- package/src/internal/reading/match.inpage.ts +2 -1
- package/src/internal/reading/outline.inpage.ts +7 -4
- package/src/internal/reading/reading.ts +5 -16
- package/src/internal/supervisor/lifecycle.ts +73 -20
- package/dist/Browser-BBSySyjT.mjs.map +0 -1
- package/dist/Page-B5cjaUp5.mjs.map +0 -1
- package/dist/Plan-CXA2BW-G.mjs.map +0 -1
- package/dist/Supervisor-BE2sGHBK.mjs.map +0 -1
- package/dist/context-BAjRj7Xo.mjs.map +0 -1
- package/dist/page-B92QnRfT.mjs.map +0 -1
|
@@ -32,12 +32,11 @@ const partsOf = (page: PageContext, bridge: Bridge, viewport: Viewport) => {
|
|
|
32
32
|
|
|
33
33
|
return {
|
|
34
34
|
page,
|
|
35
|
-
bridge,
|
|
36
35
|
...settling,
|
|
37
36
|
viewportFor: viewport.viewportFor,
|
|
38
37
|
perform: Perform.make(page, dispatch),
|
|
39
38
|
...guard,
|
|
40
|
-
...Targets.make(
|
|
39
|
+
...Targets.make(viewport, guard, pointer, dispatch, settling),
|
|
41
40
|
...pointer,
|
|
42
41
|
...Keyboard.make(page, bridge, dispatch),
|
|
43
42
|
sendMouse: dispatch.sendMouse,
|
|
@@ -110,16 +109,19 @@ const click = (input: Parts) => {
|
|
|
110
109
|
yield* marks.at(point);
|
|
111
110
|
yield* marks.on(resolved);
|
|
112
111
|
yield* moveTo("click", marks, point, resolved.cursor);
|
|
113
|
-
|
|
112
|
+
yield* Guard.check(approval, { presses: [{ index: 0, ...point }] });
|
|
114
113
|
const before = mark();
|
|
115
114
|
|
|
116
115
|
yield* marks.sent;
|
|
117
116
|
for (let index = 1; index <= count; index++) {
|
|
118
117
|
// An earlier click of a multi-click can replace the approved control under the
|
|
119
118
|
// pointer. Under a guard, its handlers run before the next press is checked and sent.
|
|
120
|
-
if (
|
|
121
|
-
yield*
|
|
122
|
-
|
|
119
|
+
if (index > 1)
|
|
120
|
+
yield* Guard.check(
|
|
121
|
+
approval,
|
|
122
|
+
{ presses: [{ index: 0, ...point }] },
|
|
123
|
+
flush("click", marks.input),
|
|
124
|
+
).pipe(
|
|
123
125
|
Effect.catchIf(
|
|
124
126
|
(error) => error.reason._tag === "StaleRef",
|
|
125
127
|
() =>
|
|
@@ -132,7 +134,6 @@ const click = (input: Parts) => {
|
|
|
132
134
|
),
|
|
133
135
|
),
|
|
134
136
|
);
|
|
135
|
-
}
|
|
136
137
|
yield* sendMouse("click", marks.input, {
|
|
137
138
|
type: "mousePressed",
|
|
138
139
|
...point,
|
|
@@ -236,13 +237,12 @@ const drag = (input: Parts) => {
|
|
|
236
237
|
yield* moveTo("drag", marks, start.point, start.cursor);
|
|
237
238
|
// Both ends are checked before the button goes down: once it is down, a release cannot
|
|
238
239
|
// be withheld, and a dragged element under the pointer would hide the drop target.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
});
|
|
240
|
+
yield* Guard.check(approval, {
|
|
241
|
+
presses: [
|
|
242
|
+
{ index: 0, ...start.point },
|
|
243
|
+
{ index: 1, ...end.point },
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
246
|
yield* marks.sent;
|
|
247
247
|
yield* sendMouse("drag", marks.input, {
|
|
248
248
|
type: "mousePressed",
|
|
@@ -267,7 +267,7 @@ const drag = (input: Parts) => {
|
|
|
267
267
|
|
|
268
268
|
const typeText = (input: Parts) => {
|
|
269
269
|
const { settings, now } = input.page;
|
|
270
|
-
const { perform, preparePolicy, mutate, targetFor, moveTo, sendMouse, flush,
|
|
270
|
+
const { perform, preparePolicy, mutate, targetFor, moveTo, sendMouse, flush, evaluate } = input;
|
|
271
271
|
const { currentDocument, sameDocument, keyStroke, typeEvent, mark, settle } = input;
|
|
272
272
|
|
|
273
273
|
return (text: string, typeOptions: TypeOptions = {}, style: Style = plain) =>
|
|
@@ -301,11 +301,9 @@ const typeText = (input: Parts) => {
|
|
|
301
301
|
// those before any input, on every path.
|
|
302
302
|
const asked = scriptCall("typeable", into ?? null, typeOptions.secret === true);
|
|
303
303
|
|
|
304
|
-
const typeable = yield* (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
: mutate("type", asked, approval)
|
|
308
|
-
).pipe(Effect.flatMap(decodeWith("type", Script.TypeableResultSchema)));
|
|
304
|
+
const typeable = yield* evaluate("type", asked, approval).pipe(
|
|
305
|
+
Effect.flatMap(decodeWith("type", Script.TypeableResultSchema)),
|
|
306
|
+
);
|
|
309
307
|
|
|
310
308
|
if ("error" in typeable)
|
|
311
309
|
return yield* failWith(
|
|
@@ -327,8 +325,7 @@ const typeText = (input: Parts) => {
|
|
|
327
325
|
// Viewers see the pointer click into the field.
|
|
328
326
|
if (marks.style.shown) {
|
|
329
327
|
yield* moveTo("type", marks, target.point, target.cursor);
|
|
330
|
-
|
|
331
|
-
yield* approval.check({ presses: [{ index: 0, ...target.point }] });
|
|
328
|
+
yield* Guard.check(approval, { presses: [{ index: 0, ...target.point }] });
|
|
332
329
|
yield* marks.sent;
|
|
333
330
|
for (const type of ["mousePressed", "mouseReleased"] as const)
|
|
334
331
|
yield* sendMouse("type", marks.input, {
|
|
@@ -354,7 +351,7 @@ const typeText = (input: Parts) => {
|
|
|
354
351
|
secret ||= focused.secret;
|
|
355
352
|
}
|
|
356
353
|
if (!secret) yield* marks.reveal;
|
|
357
|
-
|
|
354
|
+
yield* Guard.check(approval, { focused: true });
|
|
358
355
|
yield* marks.sent;
|
|
359
356
|
|
|
360
357
|
// A key's handlers can move focus, and later keys then reach a control nobody approved.
|
|
@@ -362,10 +359,7 @@ const typeText = (input: Parts) => {
|
|
|
362
359
|
// handler can split, and keys typed one at a time check that the field still has focus,
|
|
363
360
|
// once the keys before are answered, before each space, which could press a button, and
|
|
364
361
|
// after the last key.
|
|
365
|
-
const focusHeld =
|
|
366
|
-
approval === undefined
|
|
367
|
-
? Effect.void
|
|
368
|
-
: flush("type", marks.input).pipe(Effect.andThen(approval.check({ focused: true })));
|
|
362
|
+
const focusHeld = Guard.check(approval, { focused: true }, flush("type", marks.input));
|
|
369
363
|
|
|
370
364
|
if (text === "" && replace && into !== undefined) {
|
|
371
365
|
yield* sameDocument("type", since);
|
|
@@ -396,7 +390,7 @@ const typeText = (input: Parts) => {
|
|
|
396
390
|
yield* flush("type", marks.input);
|
|
397
391
|
if (typeOptions.submit === true) {
|
|
398
392
|
// Typing can move focus or change the form; Enter goes only to the approved field.
|
|
399
|
-
|
|
393
|
+
yield* Guard.check(approval, { focused: true });
|
|
400
394
|
yield* sameDocument("type", since);
|
|
401
395
|
const before = mark();
|
|
402
396
|
|
|
@@ -447,7 +441,7 @@ const press = (input: Parts) => {
|
|
|
447
441
|
);
|
|
448
442
|
|
|
449
443
|
if ("error" in focused) return yield* Guard.editFailure("press", on, focused);
|
|
450
|
-
|
|
444
|
+
yield* Guard.check(approval, { focused: true });
|
|
451
445
|
}
|
|
452
446
|
const before = mark();
|
|
453
447
|
let due = now();
|
|
@@ -456,10 +450,8 @@ const press = (input: Parts) => {
|
|
|
456
450
|
for (let index = 0; index < times; index++) {
|
|
457
451
|
// A repeated key stays in the document it began in. Under a guard, a repeated Enter
|
|
458
452
|
// or Space must also reach the approved element after the previous press settled.
|
|
459
|
-
if (index > 0 &&
|
|
460
|
-
yield* flush("press", marks.input);
|
|
461
|
-
yield* approval.check({ focused: true });
|
|
462
|
-
}
|
|
453
|
+
if (index > 0 && activates)
|
|
454
|
+
yield* Guard.check(approval, { focused: true }, flush("press", marks.input));
|
|
463
455
|
yield* sameDocument("press", since);
|
|
464
456
|
yield* keyStroke("press", marks.input, parts, hold);
|
|
465
457
|
if (index + 1 < times) {
|
|
@@ -12,20 +12,19 @@ import {
|
|
|
12
12
|
TextInserted,
|
|
13
13
|
WheelScrolled,
|
|
14
14
|
} from "../../BrowserEvent.ts";
|
|
15
|
+
import type { Point } from "../../Motion.ts";
|
|
15
16
|
import { type PageContext, reasonOf, undispatched } from "../page/context.ts";
|
|
16
17
|
import * as BrowserClock from "../pictures/clock.ts";
|
|
17
18
|
import type * as Replies from "./replies.ts";
|
|
18
19
|
|
|
19
|
-
export
|
|
20
|
+
export interface MouseEvent extends Point {
|
|
20
21
|
readonly type: "mouseMoved" | "mousePressed" | "mouseReleased" | "mouseWheel";
|
|
21
|
-
readonly x: number;
|
|
22
|
-
readonly y: number;
|
|
23
22
|
readonly button?: "none" | "left" | "right" | "middle";
|
|
24
23
|
readonly buttons?: number;
|
|
25
24
|
readonly clickCount?: number;
|
|
26
25
|
readonly deltaX?: number;
|
|
27
26
|
readonly deltaY?: number;
|
|
28
|
-
}
|
|
27
|
+
}
|
|
29
28
|
|
|
30
29
|
export const make = (page: PageContext) => {
|
|
31
30
|
const { id, pointer, publish, now, noteInput, closedBy } = page;
|
|
@@ -10,22 +10,16 @@ import type { ContextReader } from "../reading/context.inpage.ts";
|
|
|
10
10
|
import type { Names } from "../reading/names.inpage.ts";
|
|
11
11
|
import type { Guard } from "./guard.inpage.ts";
|
|
12
12
|
|
|
13
|
-
export type EditResult =
|
|
14
|
-
| { readonly ok: true; readonly detail: string }
|
|
15
|
-
| { readonly error: string; readonly stale?: boolean };
|
|
13
|
+
export type EditResult = typeof EditResultSchema.Encoded;
|
|
16
14
|
|
|
17
15
|
/** A focused field, and whether it is secret. */
|
|
18
|
-
export type FocusResult =
|
|
19
|
-
| { readonly ok: true; readonly detail: string; readonly secret: boolean }
|
|
20
|
-
| { readonly error: string; readonly stale?: boolean };
|
|
16
|
+
export type FocusResult = typeof FocusResultSchema.Encoded;
|
|
21
17
|
|
|
22
18
|
/**
|
|
23
19
|
* Whether text may be typed, and whether its field is secret. Typing into focus also names the
|
|
24
20
|
* focused field, when it is one, so the action records what it typed into.
|
|
25
21
|
*/
|
|
26
|
-
export type TypeableResult =
|
|
27
|
-
| { readonly ok: true; readonly secret: boolean; readonly subject?: typeof Subject.Encoded }
|
|
28
|
-
| { readonly error: "stale" | "untypeable"; readonly detail: string };
|
|
22
|
+
export type TypeableResult = typeof TypeableResultSchema.Encoded;
|
|
29
23
|
|
|
30
24
|
export const edit = (names: Names, guard: Guard, placing: ContextReader) => {
|
|
31
25
|
const {
|
|
@@ -7,28 +7,10 @@ import { Schema } from "effect";
|
|
|
7
7
|
import type { ContextReader } from "../reading/context.inpage.ts";
|
|
8
8
|
import type { Names } from "../reading/names.inpage.ts";
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
readonly type: string;
|
|
12
|
-
readonly name: string;
|
|
13
|
-
readonly autocomplete?: string | undefined;
|
|
14
|
-
readonly filled: boolean;
|
|
15
|
-
}
|
|
10
|
+
export type FormField = typeof FormFieldSchema.Encoded;
|
|
16
11
|
|
|
17
12
|
/** Page text about the first target. It never includes what a field holds. */
|
|
18
|
-
export
|
|
19
|
-
readonly title: string;
|
|
20
|
-
readonly description?: string | undefined;
|
|
21
|
-
readonly dialog?: string | undefined;
|
|
22
|
-
readonly heading?: string | undefined;
|
|
23
|
-
readonly nearby?: string | undefined;
|
|
24
|
-
readonly form?:
|
|
25
|
-
| {
|
|
26
|
-
readonly method: string;
|
|
27
|
-
readonly action: string;
|
|
28
|
-
readonly fields: ReadonlyArray<FormField>;
|
|
29
|
-
}
|
|
30
|
-
| undefined;
|
|
31
|
-
}
|
|
13
|
+
export type Evidence = typeof EvidenceSchema.Encoded;
|
|
32
14
|
|
|
33
15
|
type Field = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
34
16
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Schema } from "effect";
|
|
7
7
|
|
|
8
|
+
import type { Point } from "../../Motion.ts";
|
|
8
9
|
import type { Names } from "../reading/names.inpage.ts";
|
|
9
10
|
import { type Evidence, type EvidenceReader, EvidenceSchema } from "./evidence.inpage.ts";
|
|
10
11
|
import type { Targets } from "./targets.inpage.ts";
|
|
@@ -23,48 +24,26 @@ export type Fact = typeof Fact.Type;
|
|
|
23
24
|
|
|
24
25
|
export interface InputPlan {
|
|
25
26
|
readonly action: string;
|
|
26
|
-
readonly targets: ReadonlyArray<string |
|
|
27
|
+
readonly targets: ReadonlyArray<string | Point | null>;
|
|
27
28
|
readonly submit: boolean;
|
|
28
29
|
readonly keys: string | null;
|
|
29
30
|
readonly destination: string | null;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
export
|
|
33
|
-
readonly ref: string;
|
|
34
|
-
readonly element: string;
|
|
35
|
-
readonly role: string | null;
|
|
36
|
-
readonly name: string;
|
|
37
|
-
readonly cursor: string;
|
|
38
|
-
readonly href?: string | undefined;
|
|
39
|
-
/** A field the DOM marks as holding a password, a one-time code or a card's details. */
|
|
40
|
-
readonly secret: boolean;
|
|
41
|
-
readonly fingerprint: string;
|
|
42
|
-
}
|
|
33
|
+
export type InspectedTarget = NonNullable<PreparedInput["targets"][number]>;
|
|
43
34
|
|
|
44
|
-
export
|
|
45
|
-
readonly url: string;
|
|
46
|
-
readonly targets: ReadonlyArray<InspectedTarget | null>;
|
|
47
|
-
readonly facts: ReadonlyArray<Fact>;
|
|
48
|
-
readonly destination?: string | undefined;
|
|
49
|
-
readonly evidence: Evidence;
|
|
50
|
-
}
|
|
35
|
+
export type PreparedInput = Exclude<PreparedInputResult, InputFailure>;
|
|
51
36
|
|
|
52
37
|
/** A refusal; a stale target is named by its index in the plan. */
|
|
53
|
-
export
|
|
54
|
-
readonly error: string;
|
|
55
|
-
readonly detail: string;
|
|
56
|
-
readonly index?: number | undefined;
|
|
57
|
-
}
|
|
38
|
+
export type InputFailure = typeof InputPreparationError.Encoded;
|
|
58
39
|
|
|
59
|
-
export type PreparedInputResult =
|
|
40
|
+
export type PreparedInputResult = typeof PreparedInputResultSchema.Encoded;
|
|
60
41
|
|
|
61
|
-
export type ValidatedInputResult =
|
|
42
|
+
export type ValidatedInputResult = typeof ValidatedInputResultSchema.Encoded;
|
|
62
43
|
|
|
63
44
|
/** A point where a target, by its index in the plan, is about to receive a press. */
|
|
64
|
-
export interface Press {
|
|
45
|
+
export interface Press extends Point {
|
|
65
46
|
readonly index: number;
|
|
66
|
-
readonly x: number;
|
|
67
|
-
readonly y: number;
|
|
68
47
|
}
|
|
69
48
|
|
|
70
49
|
export interface ValidationOptions {
|
|
@@ -587,6 +566,7 @@ export const PreparedInputResultSchema = Schema.Union([
|
|
|
587
566
|
name: Schema.String,
|
|
588
567
|
cursor: Schema.String,
|
|
589
568
|
href: Schema.optional(Schema.String),
|
|
569
|
+
/** A field the DOM marks as holding a password, a one-time code or a card's details. */
|
|
590
570
|
secret: Schema.Boolean,
|
|
591
571
|
fingerprint: Schema.String,
|
|
592
572
|
}),
|
|
@@ -22,6 +22,13 @@ export interface PolicyPlan {
|
|
|
22
22
|
readonly validate: Effect.Effect<Approval, BrowserError>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Recheck an approval, after any pending input it must see; neither runs without a guard. */
|
|
26
|
+
export const check = (
|
|
27
|
+
approval: Approval | undefined,
|
|
28
|
+
options: Script.ValidationOptions = {},
|
|
29
|
+
before: Effect.Effect<void, BrowserError> = Effect.void,
|
|
30
|
+
) => (approval === undefined ? Effect.void : before.pipe(Effect.andThen(approval.check(options))));
|
|
31
|
+
|
|
25
32
|
// A ref that names nothing is stale on every path, named by the target that failed.
|
|
26
33
|
export const inputFailure = (
|
|
27
34
|
operation: string,
|
|
@@ -178,7 +185,11 @@ export const make = (page: PageContext, bridge: Bridge) => {
|
|
|
178
185
|
),
|
|
179
186
|
);
|
|
180
187
|
|
|
181
|
-
|
|
188
|
+
/** A read may follow the current document only when no approval binds it to one. */
|
|
189
|
+
const evaluate = (operation: string, call: string, approval: Approval | undefined) =>
|
|
190
|
+
approval === undefined ? bridge.evaluate(operation, call) : mutate(operation, call, approval);
|
|
191
|
+
|
|
192
|
+
return { preparePolicy, mutate, evaluate };
|
|
182
193
|
};
|
|
183
194
|
|
|
184
195
|
export type Guard = ReturnType<typeof make>;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { Schema } from "effect";
|
|
7
7
|
|
|
8
8
|
import { Box, SubjectContext } from "../../BrowserEvent.ts";
|
|
9
|
+
import { Point } from "../../Motion.ts";
|
|
9
10
|
import type { ContextReader } from "../reading/context.inpage.ts";
|
|
10
11
|
import type { Names } from "../reading/names.inpage.ts";
|
|
11
12
|
import type { Walk } from "../reading/walk.inpage.ts";
|
|
@@ -78,10 +79,7 @@ export const targets = (names: Names, walked: Walk, placing: ContextReader) => {
|
|
|
78
79
|
};
|
|
79
80
|
};
|
|
80
81
|
|
|
81
|
-
const point = (
|
|
82
|
-
target: string | { readonly x: number; readonly y: number },
|
|
83
|
-
scroll = true,
|
|
84
|
-
): PointResult => {
|
|
82
|
+
const point = (target: string | Point, scroll = true): PointResult => {
|
|
85
83
|
if (typeof target !== "string") {
|
|
86
84
|
const { x, y } = target;
|
|
87
85
|
|
|
@@ -263,8 +261,7 @@ export const targets = (names: Names, walked: Walk, placing: ContextReader) => {
|
|
|
263
261
|
export type Targets = ReturnType<typeof targets>;
|
|
264
262
|
|
|
265
263
|
const ResolvedPointSchema = Schema.Struct({
|
|
266
|
-
|
|
267
|
-
y: Schema.Finite,
|
|
264
|
+
...Point.fields,
|
|
268
265
|
element: Schema.String,
|
|
269
266
|
tag: Schema.String,
|
|
270
267
|
role: Schema.NullOr(Schema.String),
|
|
@@ -285,8 +282,7 @@ export const PointResultSchema = Schema.Union([
|
|
|
285
282
|
|
|
286
283
|
export const ScrollPlanSchema = Schema.NullOr(
|
|
287
284
|
Schema.Struct({
|
|
288
|
-
|
|
289
|
-
y: Schema.Finite,
|
|
285
|
+
...Point.fields,
|
|
290
286
|
dx: Schema.Finite,
|
|
291
287
|
dy: Schema.Finite,
|
|
292
288
|
}),
|
|
@@ -7,11 +7,11 @@ import { Effect } from "effect";
|
|
|
7
7
|
|
|
8
8
|
import { InvalidRequest, NotActionable, StaleRef } from "../../BrowserError.ts";
|
|
9
9
|
import { ResolvedTarget, type Target } from "../../Page.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { scriptCall } from "../page/bridge.ts";
|
|
11
11
|
import { decodeWith, failWith } from "../page/context.ts";
|
|
12
12
|
import type { Viewport } from "../page/viewport.ts";
|
|
13
13
|
import type { Dispatch } from "./dispatch.ts";
|
|
14
|
-
import type
|
|
14
|
+
import { type Approval, check, type Guard } from "./guard.ts";
|
|
15
15
|
import type { InputMarks } from "./perform.ts";
|
|
16
16
|
import type { Pointer } from "./pointer.ts";
|
|
17
17
|
import type { Settle } from "./settle.ts";
|
|
@@ -45,15 +45,13 @@ const resolvedTarget = (result: Script.ResolvedPoint): ResolvedTarget =>
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
export const make = (
|
|
48
|
-
bridge: Bridge,
|
|
49
48
|
viewport: Viewport,
|
|
50
49
|
guard: Guard,
|
|
51
50
|
pointer: Pointer,
|
|
52
51
|
dispatch: Dispatch,
|
|
53
52
|
settling: Settle,
|
|
54
53
|
) => {
|
|
55
|
-
const { evaluate } =
|
|
56
|
-
const { mutate } = guard;
|
|
54
|
+
const { evaluate, mutate } = guard;
|
|
57
55
|
const { moveTo, wheel } = pointer;
|
|
58
56
|
const { flush } = dispatch;
|
|
59
57
|
|
|
@@ -63,10 +61,9 @@ export const make = (
|
|
|
63
61
|
approval: Approval | undefined,
|
|
64
62
|
scroll: boolean,
|
|
65
63
|
) =>
|
|
66
|
-
(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
).pipe(Effect.flatMap(decodeWith(operation, Script.PointResultSchema)));
|
|
64
|
+
evaluate(operation, scriptCall("point", target, scroll), approval).pipe(
|
|
65
|
+
Effect.flatMap(decodeWith(operation, Script.PointResultSchema)),
|
|
66
|
+
);
|
|
70
67
|
|
|
71
68
|
const resolve = (operation: string, target: Target, approval?: Approval, scroll = true) => {
|
|
72
69
|
if (typeof target === "string" && !/^e\d+$/.test(target))
|
|
@@ -127,7 +124,7 @@ export const make = (
|
|
|
127
124
|
);
|
|
128
125
|
yield* flush(operation, marks.input);
|
|
129
126
|
yield* settling.settle(operation, before);
|
|
130
|
-
|
|
127
|
+
yield* check(approval);
|
|
131
128
|
}
|
|
132
129
|
|
|
133
130
|
// Wheels may be prevented or the target may need unsupported nested/frame geometry. One
|
|
@@ -137,7 +134,7 @@ export const make = (
|
|
|
137
134
|
|
|
138
135
|
yield* resolve(operation, target, approval);
|
|
139
136
|
yield* settling.settle(operation, before);
|
|
140
|
-
|
|
137
|
+
yield* check(approval);
|
|
141
138
|
|
|
142
139
|
return yield* resolve(operation, target, approval, false);
|
|
143
140
|
});
|
|
@@ -22,7 +22,7 @@ import type { BrowserEvent } from "../../BrowserEvent.ts";
|
|
|
22
22
|
import type { InputGuard, Point } from "../../Page.ts";
|
|
23
23
|
import type { Gate } from "../input/gate.ts";
|
|
24
24
|
import { Current } from "../input/replies.ts";
|
|
25
|
-
import
|
|
25
|
+
import * as BrowserClock from "../pictures/clock.ts";
|
|
26
26
|
import * as Lane from "./lane.ts";
|
|
27
27
|
import type { Openings } from "./openings.ts";
|
|
28
28
|
import * as Url from "./url.ts";
|
|
@@ -168,7 +168,7 @@ export const decodeWith =
|
|
|
168
168
|
|
|
169
169
|
export const make = (options: MakeOptions, scope: Scope.Scope) => {
|
|
170
170
|
const { id, clock } = options;
|
|
171
|
-
const now = () =>
|
|
171
|
+
const now = () => BrowserClock.monotonicMillis(clock);
|
|
172
172
|
// Deadlines and pacing guard the page's lane, so page operations run on the owner's clock; a
|
|
173
173
|
// caller's clock, such as a TestClock, cannot stall or stretch them.
|
|
174
174
|
const owned = Effect.provideService(Clock.Clock, clock);
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { type BrowserError, InvalidRequest } from "../../BrowserError.ts";
|
|
19
19
|
import { BrowserPaint, CaptureStats, Frame, type ScreencastOptions } from "../../Frame.ts";
|
|
20
20
|
import { undispatched } from "../page/context.ts";
|
|
21
|
-
import { type Estimate, toHostTime, uncertaintyAt } from "./clock.ts";
|
|
21
|
+
import { type Estimate, monotonicMillis, toHostTime, uncertaintyAt } from "./clock.ts";
|
|
22
22
|
import { tally } from "./counts.ts";
|
|
23
23
|
import * as Quiescence from "./quiescence.ts";
|
|
24
24
|
import type { NativeFrame, Transport } from "./transport.ts";
|
|
@@ -154,7 +154,7 @@ const timed = (
|
|
|
154
154
|
hostTime,
|
|
155
155
|
uncertaintyMillis: uncertaintyAt(estimate, hostTime),
|
|
156
156
|
}),
|
|
157
|
-
receivedAt:
|
|
157
|
+
receivedAt: monotonicMillis(options.clock),
|
|
158
158
|
width: size.width,
|
|
159
159
|
height: size.height,
|
|
160
160
|
...generation.transport.frameTag(),
|
|
@@ -222,7 +222,7 @@ const sizeFilter = (
|
|
|
222
222
|
Queue.offerUnsafe(wake, undefined);
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
-
const now = () =>
|
|
225
|
+
const now = () => monotonicMillis(options.clock);
|
|
226
226
|
|
|
227
227
|
const adopt = (adopted: Run) => {
|
|
228
228
|
run = undefined;
|
|
@@ -470,7 +470,7 @@ export const make = (options: Options) =>
|
|
|
470
470
|
let latest = Option.none<Frame>();
|
|
471
471
|
let history: ReadonlyArray<Frame> = [];
|
|
472
472
|
const keepMillis = Duration.toMillis(options.frameHistory);
|
|
473
|
-
const now = () =>
|
|
473
|
+
const now = () => monotonicMillis(options.clock);
|
|
474
474
|
const counts = tally(now);
|
|
475
475
|
// Frames delivered, which number them so that a reader can tell what it missed.
|
|
476
476
|
let delivered = 0;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* estimate ages, and the
|
|
2
|
+
* Host monotonic milliseconds and the browser's clock read on the owner's: estimates of their
|
|
3
|
+
* offset from timed probes, how an estimate ages, and the mapping a browser's pages share.
|
|
4
|
+
* `pictures.ts` takes the probes.
|
|
4
5
|
*/
|
|
5
|
-
import { Duration, Effect, Option } from "effect";
|
|
6
|
+
import { type Clock, Duration, Effect, Option } from "effect";
|
|
7
|
+
|
|
8
|
+
/** Read the captured owner's clock, including from native callbacks, without changing its origin. */
|
|
9
|
+
export const monotonicMillis = (clock: Clock.Clock): number =>
|
|
10
|
+
Number(clock.monotonicTimeNanosUnsafe()) / 1e6;
|
|
6
11
|
|
|
7
12
|
export interface Probe {
|
|
8
13
|
readonly hostStart: number;
|
|
@@ -8,6 +8,7 @@ import { Duration, Effect, Option, Schedule, Schema } from "effect";
|
|
|
8
8
|
|
|
9
9
|
import { BrowserError, Failed, InvalidRequest } from "../../BrowserError.ts";
|
|
10
10
|
import { Frame, Image, Screenshot } from "../../Frame.ts";
|
|
11
|
+
import type { Point } from "../../Motion.ts";
|
|
11
12
|
import { type FrameOptions, Region, type ScreenshotOptions, Zoom } from "../../Page.ts";
|
|
12
13
|
import { type Bridge, scriptCall } from "../page/bridge.ts";
|
|
13
14
|
import { decodeWith, failWith, type PageContext, reasonOf, undispatched } from "../page/context.ts";
|
|
@@ -219,7 +220,7 @@ const camera = (
|
|
|
219
220
|
operation: string,
|
|
220
221
|
format: Format,
|
|
221
222
|
quality: number,
|
|
222
|
-
clip?: Capture.Size &
|
|
223
|
+
clip?: Capture.Size & Point & { readonly scale: number },
|
|
223
224
|
) =>
|
|
224
225
|
take(operation, format, () =>
|
|
225
226
|
send("Page.captureScreenshot", {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* read to; and a condition `Page.waitFor` waits for. See `names.inpage.ts` for what a page-side part
|
|
4
4
|
* may use.
|
|
5
5
|
*/
|
|
6
|
+
import { Schema } from "effect";
|
|
7
|
+
|
|
6
8
|
import type { Control } from "../../Snapshot.ts";
|
|
7
9
|
import type { Names } from "./names.inpage.ts";
|
|
8
10
|
import type { Subjects } from "./subjects.inpage.ts";
|
|
@@ -17,7 +19,7 @@ export interface ConditionRequest {
|
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/** Whether the condition held by the deadline, or why the selector is not CSS. */
|
|
20
|
-
export type ConditionResult =
|
|
22
|
+
export type ConditionResult = typeof ConditionResultSchema.Encoded;
|
|
21
23
|
|
|
22
24
|
export const controls = (names: Names, walked: Walk, subjects: Subjects, texts: Texts) => {
|
|
23
25
|
const { clean, isDisabled, isHtml, isInput, isSelect, isTextArea, refFor, textOf } = names;
|
|
@@ -156,3 +158,8 @@ export const controls = (names: Names, walked: Walk, subjects: Subjects, texts:
|
|
|
156
158
|
};
|
|
157
159
|
|
|
158
160
|
export type Controls = ReturnType<typeof controls>;
|
|
161
|
+
|
|
162
|
+
export const ConditionResultSchema = Schema.Union([
|
|
163
|
+
Schema.Struct({ met: Schema.Boolean }),
|
|
164
|
+
Schema.Struct({ invalid: Schema.String }),
|
|
165
|
+
]);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* its row, column, label or heading. Replay chooses among candidates by the same rule.
|
|
11
11
|
*/
|
|
12
12
|
import type { SubjectContext } from "../../BrowserEvent.ts";
|
|
13
|
+
import type { Point } from "../../Motion.ts";
|
|
13
14
|
|
|
14
15
|
/** A string, or a pattern sent to the page as its source and flags. */
|
|
15
16
|
export type Wanted = string | { readonly source: string; readonly flags: string };
|
|
@@ -19,7 +20,7 @@ export interface FindRequest {
|
|
|
19
20
|
readonly name: Wanted | null;
|
|
20
21
|
readonly text: Wanted | null;
|
|
21
22
|
readonly near: string | null;
|
|
22
|
-
readonly at:
|
|
23
|
+
readonly at: Point | null;
|
|
23
24
|
readonly scope: "viewport" | "document";
|
|
24
25
|
readonly firstRef: number;
|
|
25
26
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Schema } from "effect";
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { type Control, Snapshot } from "../../Snapshot.ts";
|
|
8
8
|
import type { addresses } from "../page/url.ts";
|
|
9
9
|
import type { Controls } from "./controls.inpage.ts";
|
|
10
10
|
import type { Names } from "./names.inpage.ts";
|
|
@@ -25,9 +25,7 @@ export interface SnapshotRequest {
|
|
|
25
25
|
* The outline as a `Snapshot` reads it, and the next ref the page may give, or why `within` is not
|
|
26
26
|
* a selector.
|
|
27
27
|
*/
|
|
28
|
-
export type SnapshotResult =
|
|
29
|
-
| { readonly snapshot: typeof Snapshot.Encoded; readonly nextRef: number }
|
|
30
|
-
| { readonly invalid: string };
|
|
28
|
+
export type SnapshotResult = typeof SnapshotResultSchema.Encoded;
|
|
31
29
|
|
|
32
30
|
/** The rule every address the library reports goes by, which a link's address goes by too. */
|
|
33
31
|
type Url = ReturnType<typeof addresses>;
|
|
@@ -254,6 +252,11 @@ export const outline = (
|
|
|
254
252
|
|
|
255
253
|
export type Outline = ReturnType<typeof outline>;
|
|
256
254
|
|
|
255
|
+
export const SnapshotResultSchema = Schema.Union([
|
|
256
|
+
Schema.Struct({ snapshot: Snapshot, nextRef: Schema.Finite }),
|
|
257
|
+
Schema.Struct({ invalid: Schema.String }),
|
|
258
|
+
]);
|
|
259
|
+
|
|
257
260
|
export const ViewportResultSchema = Schema.Struct({
|
|
258
261
|
width: Schema.Finite,
|
|
259
262
|
height: Schema.Finite,
|
|
@@ -11,28 +11,17 @@ import { Snapshot, type SnapshotOptions } from "../../Snapshot.ts";
|
|
|
11
11
|
import { type Bridge, scriptCall } from "../page/bridge.ts";
|
|
12
12
|
import { contextGone, decodeWith, failWith, type PageContext } from "../page/context.ts";
|
|
13
13
|
import * as Url from "../page/url.ts";
|
|
14
|
-
import type
|
|
14
|
+
import { type ConditionRequest, ConditionResultSchema } from "./controls.inpage.ts";
|
|
15
15
|
import type { Want } from "./expect.inpage.ts";
|
|
16
16
|
import type { FindRequest, Wanted } from "./match.inpage.ts";
|
|
17
|
-
import type
|
|
17
|
+
import { type SnapshotRequest, SnapshotResultSchema } from "./outline.inpage.ts";
|
|
18
18
|
import { type TextRequest, TextResultSchema } from "./text.inpage.ts";
|
|
19
19
|
|
|
20
|
-
// What `
|
|
21
|
-
// snapshot's selector is not CSS.
|
|
22
|
-
const SnapshotResult = Schema.Union([
|
|
23
|
-
Schema.Struct({ snapshot: Snapshot, nextRef: Schema.Finite }),
|
|
24
|
-
Schema.Struct({ invalid: Schema.String }),
|
|
25
|
-
]);
|
|
26
|
-
|
|
20
|
+
// What `find` reads back, with the next ref the page may give.
|
|
27
21
|
const FindResults = Schema.Struct({ found: Schema.Array(Found), nextRef: Schema.Finite });
|
|
28
22
|
|
|
29
23
|
const UntilResult = Schema.Struct({ met: Schema.Boolean, mark: Schema.String });
|
|
30
24
|
|
|
31
|
-
const ConditionResult = Schema.Union([
|
|
32
|
-
Schema.Struct({ met: Schema.Boolean }),
|
|
33
|
-
Schema.Struct({ invalid: Schema.String }),
|
|
34
|
-
]);
|
|
35
|
-
|
|
36
25
|
// A pattern crosses into the page as its source and flags.
|
|
37
26
|
const wanted = (value: string | RegExp | undefined): Wanted | null =>
|
|
38
27
|
value === undefined
|
|
@@ -76,7 +65,7 @@ export const make = Effect.fnUntraced(function* (page: PageContext, bridge: Brid
|
|
|
76
65
|
const request: SnapshotRequest = { ...asked, firstRef: yield* Ref.get(nextRef) };
|
|
77
66
|
|
|
78
67
|
const result = yield* evaluate("snapshot", scriptCall("snapshot", request)).pipe(
|
|
79
|
-
Effect.flatMap(decodeWith("snapshot",
|
|
68
|
+
Effect.flatMap(decodeWith("snapshot", SnapshotResultSchema)),
|
|
80
69
|
);
|
|
81
70
|
|
|
82
71
|
if ("invalid" in result)
|
|
@@ -167,7 +156,7 @@ export const make = Effect.fnUntraced(function* (page: PageContext, bridge: Brid
|
|
|
167
156
|
const unmet = () => failWith("waitFor", new Timeout({ millis }));
|
|
168
157
|
|
|
169
158
|
const look = evaluate("waitFor", scriptCall("waitUntil", request, millis)).pipe(
|
|
170
|
-
Effect.flatMap(decodeWith("waitFor",
|
|
159
|
+
Effect.flatMap(decodeWith("waitFor", ConditionResultSchema)),
|
|
171
160
|
Effect.flatMap((result) =>
|
|
172
161
|
"invalid" in result
|
|
173
162
|
? failWith("waitFor", new InvalidRequest({ detail: result.invalid }))
|