effect-browser 0.3.0-beta.15 → 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 +6 -2
- 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-DaGlQVjv.mjs → Supervisor-BgWP8o9t.mjs} +41 -17
- package/dist/Supervisor-BgWP8o9t.mjs.map +1 -0
- package/dist/{Supervisor-5tZ47hR5.d.mts → Supervisor-mVPeEUp8.d.mts} +12 -5
- package/dist/Supervisor.d.mts +1 -1
- package/dist/Supervisor.mjs +1 -1
- 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 +3 -2
- 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 +56 -13
- 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-DaGlQVjv.mjs.map +0 -1
- package/dist/context-BAjRj7Xo.mjs.map +0 -1
- package/dist/page-B92QnRfT.mjs.map +0 -1
package/package.json
CHANGED
package/src/Browser.ts
CHANGED
|
@@ -492,7 +492,7 @@ export const make = Effect.fn("Browser.make")(function* (
|
|
|
492
492
|
) {
|
|
493
493
|
const clock = yield* Clock.Clock;
|
|
494
494
|
const connection = yield* Identity.make;
|
|
495
|
-
const now = () =>
|
|
495
|
+
const now = () => BrowserClock.monotonicMillis(clock);
|
|
496
496
|
const { settings, eventHistory, maxPages } = yield* settingsOf(options);
|
|
497
497
|
// Measured on first need: the first capture of any page measures it, and input never waits.
|
|
498
498
|
const mapping = BrowserClock.mapping(now);
|
package/src/BrowserEvent.ts
CHANGED
|
@@ -119,8 +119,7 @@ const Button = Schema.Literals(["left", "right", "middle"]);
|
|
|
119
119
|
|
|
120
120
|
/** An element's box in viewport CSS pixels; it may lie partly or wholly outside the viewport. */
|
|
121
121
|
export const Box = Schema.Struct({
|
|
122
|
-
|
|
123
|
-
y: Schema.Finite,
|
|
122
|
+
...Motion.Point.fields,
|
|
124
123
|
width: Schema.Finite,
|
|
125
124
|
height: Schema.Finite,
|
|
126
125
|
});
|
|
@@ -169,8 +168,8 @@ export class Action extends Schema.TaggedClass<Action>()("Action", {
|
|
|
169
168
|
/** For an action at a point, the box of what it found there. */
|
|
170
169
|
box: Schema.optional(Box),
|
|
171
170
|
text: Schema.optional(Schema.String),
|
|
172
|
-
x: Schema.optional(
|
|
173
|
-
y: Schema.optional(
|
|
171
|
+
x: Schema.optional(Motion.Point.fields.x),
|
|
172
|
+
y: Schema.optional(Motion.Point.fields.y),
|
|
174
173
|
ok: Schema.Boolean,
|
|
175
174
|
dispatched: Schema.Boolean,
|
|
176
175
|
error: Schema.optional(Schema.String),
|
|
@@ -196,16 +195,14 @@ export class TrackPerformed extends Schema.TaggedClass<TrackPerformed>()("TrackP
|
|
|
196
195
|
page: Schema.String,
|
|
197
196
|
plan: Sequence,
|
|
198
197
|
dispatched: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
199
|
-
|
|
200
|
-
y: Schema.Finite,
|
|
198
|
+
...Motion.Point.fields,
|
|
201
199
|
complete: Schema.Boolean,
|
|
202
200
|
}) {}
|
|
203
201
|
|
|
204
202
|
export class PointerPressed extends Schema.TaggedClass<PointerPressed>()("PointerPressed", {
|
|
205
203
|
at: Schema.Finite,
|
|
206
204
|
page: Schema.String,
|
|
207
|
-
|
|
208
|
-
y: Schema.Finite,
|
|
205
|
+
...Motion.Point.fields,
|
|
209
206
|
button: Button,
|
|
210
207
|
clickCount: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
211
208
|
}) {}
|
|
@@ -213,8 +210,7 @@ export class PointerPressed extends Schema.TaggedClass<PointerPressed>()("Pointe
|
|
|
213
210
|
export class PointerReleased extends Schema.TaggedClass<PointerReleased>()("PointerReleased", {
|
|
214
211
|
at: Schema.Finite,
|
|
215
212
|
page: Schema.String,
|
|
216
|
-
|
|
217
|
-
y: Schema.Finite,
|
|
213
|
+
...Motion.Point.fields,
|
|
218
214
|
button: Button,
|
|
219
215
|
clickCount: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
220
216
|
}) {}
|
|
@@ -222,8 +218,7 @@ export class PointerReleased extends Schema.TaggedClass<PointerReleased>()("Poin
|
|
|
222
218
|
export class WheelScrolled extends Schema.TaggedClass<WheelScrolled>()("WheelScrolled", {
|
|
223
219
|
at: Schema.Finite,
|
|
224
220
|
page: Schema.String,
|
|
225
|
-
|
|
226
|
-
y: Schema.Finite,
|
|
221
|
+
...Motion.Point.fields,
|
|
227
222
|
dx: Schema.Finite,
|
|
228
223
|
dy: Schema.Finite,
|
|
229
224
|
}) {}
|
package/src/Motion.ts
CHANGED
|
@@ -13,8 +13,7 @@ export const Point = Schema.Struct({ x: Schema.Finite, y: Schema.Finite });
|
|
|
13
13
|
export type Point = typeof Point.Type;
|
|
14
14
|
|
|
15
15
|
export const Sample = Schema.Struct({
|
|
16
|
-
|
|
17
|
-
y: Schema.Finite,
|
|
16
|
+
...Point.fields,
|
|
18
17
|
afterMillis: Schema.Finite.check(
|
|
19
18
|
Schema.isGreaterThanOrEqualTo(0),
|
|
20
19
|
Schema.isLessThanOrEqualTo(maximumDurationMillis),
|
package/src/Page.ts
CHANGED
|
@@ -27,12 +27,10 @@ import * as Guard from "./internal/input/guard.inpage.ts";
|
|
|
27
27
|
import { Correlation, FailFast } from "./internal/page/lane.ts";
|
|
28
28
|
import * as Url from "./internal/page/url.ts";
|
|
29
29
|
import type { Window } from "./Moment.ts";
|
|
30
|
+
import * as Motion from "./Motion.ts";
|
|
30
31
|
import { type Snapshot, type SnapshotOptions } from "./Snapshot.ts";
|
|
31
32
|
|
|
32
|
-
export
|
|
33
|
-
readonly x: number;
|
|
34
|
-
readonly y: number;
|
|
35
|
-
}
|
|
33
|
+
export type Point = Motion.Point;
|
|
36
34
|
|
|
37
35
|
/** A ref from a snapshot, such as `"e12"`, or a viewport point. */
|
|
38
36
|
export type Target = string | Point;
|
|
@@ -54,7 +52,7 @@ export class Zoom extends Schema.Class<Zoom>("effect-browser/Zoom")({
|
|
|
54
52
|
|
|
55
53
|
/** The target as it read before input. Point coordinates are never snapped to another position. */
|
|
56
54
|
export class ResolvedTarget extends Schema.Class<ResolvedTarget>("effect-browser/ResolvedTarget")({
|
|
57
|
-
point:
|
|
55
|
+
point: Motion.Point,
|
|
58
56
|
element: Schema.String,
|
|
59
57
|
/** The element's lowercase tag name. */
|
|
60
58
|
tag: Schema.String,
|
|
@@ -131,9 +129,7 @@ export interface ScreenshotOptions {
|
|
|
131
129
|
/** The oldest a reused frame may be, as for `frame`. Defaults to 250 milliseconds. */
|
|
132
130
|
readonly maxAge?: Duration.Input | undefined;
|
|
133
131
|
/** A region of the viewport, captured in CSS pixels at the viewport's own scale; always new. */
|
|
134
|
-
readonly clip?:
|
|
135
|
-
| { readonly x: number; readonly y: number; readonly width: number; readonly height: number }
|
|
136
|
-
| undefined;
|
|
132
|
+
readonly clip?: Box | undefined;
|
|
137
133
|
/** JPEG quality, 0 to 100. Defaults to 80. */
|
|
138
134
|
readonly quality?: number | undefined;
|
|
139
135
|
/** `"jpeg"`, the default, or `"png"`, which is always a new picture. */
|
|
@@ -335,7 +331,7 @@ export class InputRequest extends Schema.Class<InputRequest>("effect-browser/Inp
|
|
|
335
331
|
target: Schema.optional(Schema.String),
|
|
336
332
|
element: Schema.optional(Schema.String),
|
|
337
333
|
/** Literal pixel targets only; ref coordinates are resolved after the policy allows them. */
|
|
338
|
-
point: Schema.optional(
|
|
334
|
+
point: Schema.optional(Motion.Point),
|
|
339
335
|
text: Schema.optional(Schema.String),
|
|
340
336
|
role: Schema.optional(Schema.NullOr(Schema.String)),
|
|
341
337
|
name: Schema.optional(Schema.String),
|
package/src/Plan.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "./BrowserEvent.ts";
|
|
34
34
|
import { undispatched } from "./internal/page/context.ts";
|
|
35
35
|
import { Ambiguous, choose, Drifted, Missing } from "./internal/reading/choose.ts";
|
|
36
|
+
import { Point as ViewportPoint } from "./Motion.ts";
|
|
36
37
|
import {
|
|
37
38
|
type Found,
|
|
38
39
|
type Page,
|
|
@@ -44,8 +45,6 @@ import {
|
|
|
44
45
|
|
|
45
46
|
export { Ambiguous, Drifted, Missing } from "./internal/reading/choose.ts";
|
|
46
47
|
|
|
47
|
-
const ViewportPoint = Schema.Struct({ x: Schema.Finite, y: Schema.Finite });
|
|
48
|
-
|
|
49
48
|
export const StepAction = Schema.Literals([
|
|
50
49
|
"navigate",
|
|
51
50
|
"back",
|
package/src/Presentation.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { Clock, Duration, Effect, Fiber, MutableRef, Option, Semaphore } from "e
|
|
|
20
20
|
import type { BrowserError } from "./BrowserError.ts";
|
|
21
21
|
import * as Style from "./internal/input/style.ts";
|
|
22
22
|
import { type Internals, internalsOf, viewOf } from "./internal/page/page.ts";
|
|
23
|
+
import { monotonicMillis } from "./internal/pictures/clock.ts";
|
|
23
24
|
import * as Motion from "./Motion.ts";
|
|
24
25
|
import type { Page, Point, Target } from "./Page.ts";
|
|
25
26
|
|
|
@@ -81,7 +82,7 @@ export const make = Effect.fn("Presentation.make")(function* (options: Options =
|
|
|
81
82
|
const pacing = options.pacing ?? human;
|
|
82
83
|
const clock = yield* Clock.Clock;
|
|
83
84
|
const scope = yield* Effect.scope;
|
|
84
|
-
const now = () =>
|
|
85
|
+
const now = () => monotonicMillis(clock);
|
|
85
86
|
const pointer = MutableRef.make(Option.none<Point>());
|
|
86
87
|
const hand = yield* Semaphore.make(1);
|
|
87
88
|
|
package/src/Stage.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { type BrowserError, Closed, consequence, InvalidRequest, Timeout } from
|
|
|
26
26
|
import type { CaptureStats, Frame, ScreencastOptions } from "./Frame.ts";
|
|
27
27
|
import { failWith, undispatched } from "./internal/page/context.ts";
|
|
28
28
|
import { internalsOf } from "./internal/page/page.ts";
|
|
29
|
+
import { monotonicMillis } from "./internal/pictures/clock.ts";
|
|
29
30
|
import type { Page } from "./Page.ts";
|
|
30
31
|
|
|
31
32
|
export interface Options extends ScreencastOptions {
|
|
@@ -390,7 +391,7 @@ export const make = Effect.fn("Stage.make")(function* (
|
|
|
390
391
|
): Effect.fn.Return<Stage, BrowserError, Scope.Scope> {
|
|
391
392
|
const clock = yield* Clock.Clock;
|
|
392
393
|
const owner = yield* Effect.scope;
|
|
393
|
-
const now = () =>
|
|
394
|
+
const now = () => monotonicMillis(clock);
|
|
394
395
|
const waited = Duration.fromInput(options.firstFrame ?? Duration.seconds(5));
|
|
395
396
|
|
|
396
397
|
if (
|
package/src/Supervisor.ts
CHANGED
|
@@ -107,7 +107,8 @@ export interface Admission {
|
|
|
107
107
|
/**
|
|
108
108
|
* Optional service supplied during `open`. Providers report failed-attempt cleanup here before
|
|
109
109
|
* their scope closes; successful generations instead return `Opened.release`. Reporting does
|
|
110
|
-
* not release resources or replace the original acquisition failure.
|
|
110
|
+
* not release resources or replace the original acquisition failure. Terminal `Down` and opening
|
|
111
|
+
* `Closed` states publish the reported outcomes of their own generation's tries.
|
|
111
112
|
*/
|
|
112
113
|
export class Releasing extends Context.Service<
|
|
113
114
|
Releasing,
|
|
@@ -556,7 +557,7 @@ const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
|
|
|
556
557
|
return yield* options.open.pipe(
|
|
557
558
|
Effect.provideService(Releasing, {
|
|
558
559
|
released: (released) =>
|
|
559
|
-
apply({ _tag: "AttemptReleased", released }).pipe(Effect.asVoid),
|
|
560
|
+
apply({ _tag: "AttemptReleased", number, released }).pipe(Effect.asVoid),
|
|
560
561
|
}),
|
|
561
562
|
);
|
|
562
563
|
}),
|
|
@@ -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);
|