effect-browser 0.3.0-beta.8 → 0.3.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +27 -0
  2. package/dist/{Browser-CNwK0r0q.mjs → Browser-Bc_LIqTC.mjs} +184 -16
  3. package/dist/Browser-Bc_LIqTC.mjs.map +1 -0
  4. package/dist/Browser.d.mts +16 -4
  5. package/dist/Browser.mjs +2 -2
  6. package/dist/{BrowserError-DXXIA6Rk.d.mts → BrowserError-Coy7EW1N.d.mts} +10 -5
  7. package/dist/BrowserError.d.mts +2 -2
  8. package/dist/BrowserError.mjs +10 -1
  9. package/dist/BrowserError.mjs.map +1 -1
  10. package/dist/Cdp.d.mts +1 -1
  11. package/dist/Cdp.mjs +3 -3
  12. package/dist/Chromium.d.mts +1 -1
  13. package/dist/Chromium.mjs +2 -2
  14. package/dist/Expect.d.mts +2 -2
  15. package/dist/Expect.mjs +2 -2
  16. package/dist/Moment.d.mts +1 -1
  17. package/dist/Moment.mjs +1 -1
  18. package/dist/{Page-D4281dIi.mjs → Page-B5cjaUp5.mjs} +2 -2
  19. package/dist/{Page-D4281dIi.mjs.map → Page-B5cjaUp5.mjs.map} +1 -1
  20. package/dist/{Page-C30aG2RQ.d.mts → Page-BlPjfibv.d.mts} +2 -2
  21. package/dist/Page.d.mts +1 -1
  22. package/dist/Page.mjs +1 -1
  23. package/dist/{Plan-BvCS9qAf.d.mts → Plan-BI8XGvDu.d.mts} +3 -3
  24. package/dist/{Plan-CyQNv49C.mjs → Plan-D1wrJVs8.mjs} +3 -3
  25. package/dist/{Plan-CyQNv49C.mjs.map → Plan-D1wrJVs8.mjs.map} +1 -1
  26. package/dist/Plan.d.mts +1 -1
  27. package/dist/Plan.mjs +1 -1
  28. package/dist/Policy.d.mts +1 -1
  29. package/dist/Policy.mjs +1 -1
  30. package/dist/Presentation.d.mts +1 -1
  31. package/dist/Presentation.mjs +1 -1
  32. package/dist/Stage.d.mts +2 -2
  33. package/dist/Stage.mjs +2 -2
  34. package/dist/{context-DAYjATgC.mjs → context-B6xr8nVi.mjs} +134 -10
  35. package/dist/context-B6xr8nVi.mjs.map +1 -0
  36. package/dist/index.d.mts +3 -3
  37. package/dist/index.mjs +3 -3
  38. package/dist/{page-DHeYWEs-.mjs → page-D-OedyBw.mjs} +59 -126
  39. package/dist/page-D-OedyBw.mjs.map +1 -0
  40. package/dist/{url-CcL0K7S0.mjs → url-Cd04Okid.mjs} +6 -4
  41. package/dist/url-Cd04Okid.mjs.map +1 -0
  42. package/package.json +1 -1
  43. package/src/Browser.ts +57 -16
  44. package/src/BrowserError.ts +9 -0
  45. package/src/internal/input/gate.ts +224 -0
  46. package/src/internal/input/guard.ts +3 -2
  47. package/src/internal/input/perform.ts +84 -32
  48. package/src/internal/input/replies.ts +52 -16
  49. package/src/internal/page/context.ts +21 -11
  50. package/src/internal/page/lane.ts +20 -10
  51. package/src/internal/timeline/events.ts +1 -0
  52. package/dist/Browser-CNwK0r0q.mjs.map +0 -1
  53. package/dist/context-DAYjATgC.mjs.map +0 -1
  54. package/dist/page-DHeYWEs-.mjs.map +0 -1
  55. package/dist/url-CcL0K7S0.mjs.map +0 -1
@@ -1,15 +1,15 @@
1
- import { Plan, maximumSamples } from "./Motion.mjs";
1
+ import { Plan } from "./Motion.mjs";
2
2
  import { Action, Box, CursorChanged, KeyChanged, Navigated, PageLoaded, PointerPressed, PointerReleased, Subject, SubjectContext, TextInserted, TrackEvent, TrackPerformed, TrackPlanned, WheelScrolled } from "./BrowserEvent.mjs";
3
3
  import { BrowserError, Failed, InvalidRequest, NavigationFailed, NotActionable, NotFound, PolicyTimeout, StaleRef, Timeout } from "./BrowserError.mjs";
4
- import { i as redact, n as parse, s as Correlation, t as addresses } from "./url-CcL0K7S0.mjs";
5
- import { a as make$19, i as failWith, n as contextGone, o as reasonOf, r as decodeWith, s as undispatched } from "./context-DAYjATgC.mjs";
4
+ import { a as make$19, c as Current, i as failWith, l as make$18, n as contextGone, o as reasonOf, r as decodeWith, s as undispatched } from "./context-B6xr8nVi.mjs";
5
+ import { i as redact, n as parse, s as Correlation, t as addresses } from "./url-Cd04Okid.mjs";
6
6
  import { BrowserPaint, CaptureStats, Frame, Image, Screenshot } from "./Frame.mjs";
7
- import { _ as evidence, c as State, g as guard, h as ValidatedInputResultSchema, i as InputRequest, l as Text, m as PreparedInputResultSchema, o as Region, p as redacted, r as Found, s as ResolvedTarget, u as Zoom } from "./Page-D4281dIi.mjs";
7
+ import { _ as evidence, c as State, g as guard, h as ValidatedInputResultSchema, i as InputRequest, l as Text, m as PreparedInputResultSchema, o as Region, p as redacted, r as Found, s as ResolvedTarget, u as Zoom } from "./Page-B5cjaUp5.mjs";
8
8
  import { Snapshot } from "./Snapshot.mjs";
9
9
  import { t as match } from "./match.inpage-xOe9LNO0.mjs";
10
10
  import { r as Changes, t as Change } from "./Change-DNvztNLu.mjs";
11
11
  import { Window } from "./Moment.mjs";
12
- import { Clock, Data, Deferred, Duration, Effect, Exit, MutableRef, Option, Queue, Random, Ref, Result, Schedule, Schema, Scope, Semaphore, Stream, SynchronizedRef } from "effect";
12
+ import { Clock, Deferred, Duration, Effect, Exit, MutableRef, Option, Queue, Random, Ref, Result, Schedule, Schema, Scope, Semaphore, Stream, SynchronizedRef } from "effect";
13
13
  //#region src/internal/input/edit.inpage.ts
14
14
  /**
15
15
  * In the page: editing a field. It refuses text that a focused control could act on, focuses a
@@ -2166,7 +2166,7 @@ const mainFrame = (page, event) => "frame" in event ? event.frame.parentId === v
2166
2166
  } : void 0;
2167
2167
  /** A call to the page script, its arguments quoted as JavaScript literals. */
2168
2168
  const scriptCall = (name, ...args) => `${name}(${args.map((arg) => JSON.stringify(arg)).join(", ")})`;
2169
- const make$18 = Effect.fnUntraced(function* (page) {
2169
+ const make$17 = Effect.fnUntraced(function* (page) {
2170
2170
  const { id, cdp, native, span, publish, now } = page;
2171
2171
  const { send } = page.protocol;
2172
2172
  const registration = yield* SynchronizedRef.make("none");
@@ -2351,7 +2351,7 @@ const toBrowserSeconds = (calibration, hostMillis) => (hostMillis + calibration.
2351
2351
  * protocol session, stamped with the browser's clock when its run has a mapping, and is published
2352
2352
  * as it is submitted.
2353
2353
  */
2354
- const make$17 = (page) => {
2354
+ const make$16 = (page) => {
2355
2355
  const { id, pointer, publish, now, noteInput, closedBy } = page;
2356
2356
  const { send } = page.protocol;
2357
2357
  const inputClocks = /* @__PURE__ */ new WeakMap();
@@ -2457,7 +2457,7 @@ const inputFailure = (operation, targets, failure) => {
2457
2457
  return failWith(operation, failure.error === "outside" ? new InvalidRequest({ detail: failure.detail }) : failure.error === "stale" && typeof target === "string" ? new StaleRef({ ref: target }) : new NotActionable({ detail: failure.detail }));
2458
2458
  };
2459
2459
  const editFailure = (operation, ref, failure) => failWith(operation, failure.stale === true ? new StaleRef({ ref }) : new NotActionable({ detail: failure.error }));
2460
- const make$16 = (page, bridge) => {
2460
+ const make$15 = (page, bridge) => {
2461
2461
  const { id } = page;
2462
2462
  const { current, evaluateIn, evaluateWithContext } = bridge;
2463
2463
  const preparePolicy = (action, info, targets, flags = {}) => Effect.gen(function* () {
@@ -2509,7 +2509,7 @@ const make$16 = (page, bridge) => {
2509
2509
  }))
2510
2510
  };
2511
2511
  });
2512
- const mutate = (operation, call, approval, mayHaveRun = Effect.void) => (approval === void 0 ? current(operation) : Effect.succeed(approval.contextId)).pipe(Effect.flatMap((contextId) => evaluateIn(operation, call, contextId).pipe(Effect.tapError((error) => error.reason._tag === "Failed" && /Cannot find context|__effectBrowser/i.test(error.reason.detail) ? Effect.void : mayHaveRun))), Effect.catchIf(contextGone, () => failWith(operation, new NotActionable({ detail: "the page changed before the approved input could run" }))));
2512
+ const mutate = (operation, call, approval, mayHaveRun = Effect.void) => (approval === void 0 ? current(operation) : Effect.succeed(approval.contextId)).pipe(Effect.flatMap((contextId) => evaluateIn(operation, call, contextId).pipe(Effect.onInterrupt(() => mayHaveRun), Effect.tapError((error) => error.reason._tag === "Failed" && /Cannot find context|__effectBrowser/i.test(error.reason.detail) ? Effect.void : mayHaveRun))), Effect.catchIf(contextGone, () => failWith(operation, new NotActionable({ detail: "the page changed before the approved input could run" }))));
2513
2513
  return {
2514
2514
  preparePolicy,
2515
2515
  mutate
@@ -2685,7 +2685,7 @@ const description = (character) => {
2685
2685
  * Keys and typing: a key chord held and released, each typed event, and the document a run of
2686
2686
  * keys must stay in.
2687
2687
  */
2688
- const make$15 = (page, bridge, dispatch) => {
2688
+ const make$14 = (page, bridge, dispatch) => {
2689
2689
  const { playwright } = page;
2690
2690
  const { send } = page.protocol;
2691
2691
  const { currentDocument } = bridge;
@@ -2734,103 +2734,6 @@ const make$15 = (page, bridge, dispatch) => {
2734
2734
  typeEvent
2735
2735
  };
2736
2736
  };
2737
- var InputFailure = class extends Data.TaggedError("InputFailure") {};
2738
- const make$14 = () => {
2739
- const outstanding = /* @__PURE__ */ new Set();
2740
- const idle = Effect.gen(function* () {
2741
- while (outstanding.size > 0) yield* Effect.promise(() => Promise.all(outstanding));
2742
- });
2743
- /** `idle` lets an action wait, in its page's turn, for the page's earlier replies. */
2744
- return {
2745
- idle,
2746
- begin: Effect.gen(function* () {
2747
- yield* idle;
2748
- const pending = /* @__PURE__ */ new Set();
2749
- const held = [];
2750
- let failure;
2751
- let reserved = 0;
2752
- let closed = false;
2753
- const requireOpen = Effect.suspend(() => closed ? Effect.fail(new InputFailure({ cause: /* @__PURE__ */ new Error("the input run has stopped") })) : Effect.void);
2754
- const requireSuccess = Effect.suspend(() => failure === void 0 ? Effect.void : Effect.fail(failure));
2755
- const submit = (command) => {
2756
- if (reserved < 1) throw new Error("input must reserve capacity before dispatch");
2757
- reserved--;
2758
- let response;
2759
- try {
2760
- response = command();
2761
- } catch (cause) {
2762
- failure ??= new InputFailure({ cause });
2763
- return;
2764
- }
2765
- const reply = response.then(() => {
2766
- pending.delete(reply);
2767
- outstanding.delete(reply);
2768
- }, (cause) => {
2769
- failure ??= new InputFailure({ cause });
2770
- pending.delete(reply);
2771
- outstanding.delete(reply);
2772
- });
2773
- pending.add(reply);
2774
- outstanding.add(reply);
2775
- };
2776
- const reserve = (commands, maximum, limit) => Effect.gen(function* () {
2777
- yield* requireOpen;
2778
- yield* requireSuccess;
2779
- if (!Number.isInteger(commands) || commands < 1 || commands > maximum) return yield* Effect.die(/* @__PURE__ */ new Error("invalid input reservation"));
2780
- while (outstanding.size + reserved + commands > limit) {
2781
- yield* Effect.promise(() => Promise.race(outstanding));
2782
- yield* requireOpen;
2783
- yield* requireSuccess;
2784
- }
2785
- reserved += commands;
2786
- });
2787
- return {
2788
- reserve: (commands) => reserve(commands, 64, 64),
2789
- reserveMotion: (commands) => reserve(commands, maximumSamples, 64 + maximumSamples),
2790
- send: (command) => Effect.gen(function* () {
2791
- yield* requireOpen;
2792
- yield* requireSuccess;
2793
- yield* Effect.sync(() => submit(command));
2794
- yield* requireSuccess;
2795
- }),
2796
- down: (key, press, release) => Effect.gen(function* () {
2797
- yield* requireOpen;
2798
- yield* requireSuccess;
2799
- yield* Effect.sync(() => {
2800
- held.push({
2801
- key,
2802
- release
2803
- });
2804
- submit(press);
2805
- });
2806
- yield* requireSuccess;
2807
- }),
2808
- up: (key) => Effect.gen(function* () {
2809
- yield* requireOpen;
2810
- yield* Effect.sync(() => {
2811
- const index = held.findLastIndex((entry) => entry.key === key);
2812
- const entry = held[index];
2813
- if (entry === void 0) return;
2814
- held.splice(index, 1);
2815
- submit(entry.release);
2816
- });
2817
- yield* requireSuccess;
2818
- }),
2819
- drain: Effect.gen(function* () {
2820
- while (pending.size > 0) yield* Effect.promise(() => Promise.all(pending));
2821
- yield* requireSuccess;
2822
- }),
2823
- close: Effect.sync(() => {
2824
- if (closed) return;
2825
- closed = true;
2826
- for (const entry of held.toReversed()) submit(entry.release);
2827
- held.length = 0;
2828
- reserved = 0;
2829
- })
2830
- };
2831
- })
2832
- };
2833
- };
2834
2737
  //#endregion
2835
2738
  //#region src/internal/input/style.ts
2836
2739
  /**
@@ -2997,10 +2900,41 @@ const deadline$1 = (operation, timeout, now, presented) => (effect) => Effect.su
2997
2900
  });
2998
2901
  return Effect.raceFirst(effect, expiry);
2999
2902
  });
2903
+ /** An admitted operation stays owned through its Action and any unanswered native replies. */
2904
+ const admission = (page, pending, name) => {
2905
+ let run;
2906
+ let end;
2907
+ return {
2908
+ enter: Effect.uninterruptible(Effect.gen(function* () {
2909
+ const released = page.inputGate === void 0 ? void 0 : yield* page.inputGate.enter(name);
2910
+ const done = Deferred.makeUnsafe();
2911
+ pending.add(done);
2912
+ end = () => {
2913
+ const finish = () => {
2914
+ released?.();
2915
+ pending.delete(done);
2916
+ Deferred.doneUnsafe(done, Exit.void);
2917
+ };
2918
+ if (run === void 0) finish();
2919
+ else run.afterIdle(finish);
2920
+ };
2921
+ })),
2922
+ using: (current) => {
2923
+ run = current;
2924
+ },
2925
+ end: Effect.sync(() => end?.())
2926
+ };
2927
+ };
2928
+ /** Close a run on every exit while preserving its native replies for the page's next input. */
2929
+ const useRun = (page, sender, name, run, action) => action.pipe(Effect.matchEffect({
2930
+ onFailure: (error) => run.close.pipe(Effect.andThen(sender.flush(name, run).pipe(Effect.ignore)), Effect.andThen(Effect.fail(error))),
2931
+ onSuccess: (value) => run.close.pipe(Effect.andThen(sender.flush(name, run)), Effect.as(value))
2932
+ }), Effect.ensuring(run.close.pipe(Effect.andThen(page.openings.clear))), Effect.provideService(Current, run));
3000
2933
  const make$13 = (page, sender) => {
3001
2934
  const { id, settings, mapping, lane, publish, now, noteInput, span, owned } = page;
3002
- const { inputClocks, flush } = sender;
3003
- const input = make$14();
2935
+ const { inputClocks } = sender;
2936
+ const input = make$18();
2937
+ const admitted = /* @__PURE__ */ new Set();
3004
2938
  const perform = (name, info, timeout, prepare, body, style = plain) => Effect.gen(function* () {
3005
2939
  const startedAt = now();
3006
2940
  const sent = yield* Ref.make(false);
@@ -3009,6 +2943,7 @@ const make$13 = (page, sender) => {
3009
2943
  const touch = Effect.sync(noteInput);
3010
2944
  let revealed = info.secret !== true;
3011
2945
  let presented = 0;
2946
+ const ticket = admission(page, admitted, name);
3012
2947
  const marks = {
3013
2948
  style,
3014
2949
  present: (millis) => Effect.sync(() => {
@@ -3026,24 +2961,22 @@ const make$13 = (page, sender) => {
3026
2961
  const admit = input.idle.pipe(Effect.tap(() => Effect.sync(() => {
3027
2962
  estimate = info.input ?? true ? mapping.latest() : void 0;
3028
2963
  })));
3029
- const useInput = (action) => input.begin.pipe(Effect.tap((run) => Effect.sync(() => {
3030
- if (estimate !== void 0) inputClocks.set(run, estimate);
3031
- })), Effect.flatMap((run) => action(run).pipe(Effect.matchEffect({
3032
- onFailure: (error) => run.close.pipe(Effect.andThen(flush(name, run).pipe(Effect.ignore)), Effect.andThen(Effect.fail(error))),
3033
- onSuccess: (value) => run.close.pipe(Effect.andThen(flush(name, run)), Effect.as(value))
3034
- }), Effect.ensuring(run.close.pipe(Effect.andThen(page.openings.clear))))));
2964
+ const useInput = (action) => input.begin.pipe(Effect.tap((current) => Effect.sync(() => {
2965
+ ticket.using(current);
2966
+ if (estimate !== void 0) inputClocks.set(current, estimate);
2967
+ })), Effect.flatMap((run) => useRun(page, sender, name, run, action(run))));
3035
2968
  let queuedMillis;
3036
2969
  const dispatch = (action) => Effect.suspend(() => {
3037
2970
  const asked = now();
3038
- return lane.write(name, timeout)(Effect.suspend(() => admit.pipe(Effect.timeoutOrElse({
2971
+ return lane.write(name, timeout)(ticket.enter.pipe(Effect.andThen(Effect.suspend(() => admit.pipe(Effect.timeoutOrElse({
3039
2972
  duration: Duration.millis(Math.max(0, Duration.toMillis(timeout) - (now() - asked))),
3040
2973
  orElse: () => timedOut(name, timeout)
3041
- }))).pipe(Effect.tap(() => Effect.sync(() => {
2974
+ })))), Effect.tap(() => Effect.sync(() => {
3042
2975
  queuedMillis = Math.round(now() - asked);
3043
2976
  })), Effect.andThen(deadline$1(name, timeout, now, () => presented)(action))));
3044
2977
  });
3045
2978
  const guard = settings.guard;
3046
- const run = guard === void 0 ? dispatch(useInput((run) => body({
2979
+ const action = guard === void 0 ? dispatch(useInput((run) => body({
3047
2980
  ...marks,
3048
2981
  input: run
3049
2982
  }, void 0))) : Effect.gen(function* () {
@@ -3059,7 +2992,7 @@ const make$13 = (page, sender) => {
3059
2992
  });
3060
2993
  const record = info.recorded === false ? () => 0 : publish;
3061
2994
  return yield* Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
3062
- const exit = yield* Effect.exit(restore(run));
2995
+ const exit = yield* Effect.exit(restore((page.inputGate?.check(name) ?? Effect.void).pipe(Effect.andThen(action))));
3063
2996
  const dispatched = yield* Ref.get(sent);
3064
2997
  const point = yield* Ref.get(at);
3065
2998
  const { subject, to, box } = yield* Ref.get(acted);
@@ -3104,9 +3037,9 @@ const make$13 = (page, sender) => {
3104
3037
  dispatched: true
3105
3038
  }));
3106
3039
  return yield* exit;
3107
- }));
3040
+ })).pipe(Effect.ensuring(ticket.end));
3108
3041
  }).pipe(span(`Page.${name}`, info.target === void 0 ? {} : { target: info.target }), owned);
3109
- return perform;
3042
+ return Object.assign(perform, { idle: Effect.suspend(() => Effect.forEach(admitted, Deferred.await, { discard: true })) });
3110
3043
  };
3111
3044
  //#endregion
3112
3045
  //#region src/internal/input/pointer.ts
@@ -3320,9 +3253,9 @@ const buttonMask = {
3320
3253
  middle: 4
3321
3254
  };
3322
3255
  const partsOf = (page, bridge, viewport) => {
3323
- const dispatch = make$17(page);
3256
+ const dispatch = make$16(page);
3324
3257
  const pointer = make$12(page, dispatch, viewport);
3325
- const guard = make$16(page, bridge);
3258
+ const guard = make$15(page, bridge);
3326
3259
  const settling = make$11(page, bridge);
3327
3260
  return {
3328
3261
  page,
@@ -3333,7 +3266,7 @@ const partsOf = (page, bridge, viewport) => {
3333
3266
  ...guard,
3334
3267
  ...make$10(bridge, viewport, guard, pointer, dispatch, settling),
3335
3268
  ...pointer,
3336
- ...make$15(page, bridge, dispatch),
3269
+ ...make$14(page, bridge, dispatch),
3337
3270
  sendMouse: dispatch.sendMouse,
3338
3271
  flush: dispatch.flush
3339
3272
  };
@@ -5038,7 +4971,7 @@ const viewOf = (page, view) => {
5038
4971
  const make = Effect.fnUntraced(function* (options) {
5039
4972
  const page = make$19(options, yield* Effect.scope);
5040
4973
  const { id, playwright, native, owned, within, lane, span } = page;
5041
- const bridge = yield* make$18(page);
4974
+ const bridge = yield* make$17(page);
5042
4975
  const viewport = make$1(page, bridge);
5043
4976
  const input = make$9(page, bridge, viewport);
5044
4977
  const pictures = yield* make$7(page, bridge, viewport);
@@ -5107,4 +5040,4 @@ const make = Effect.fnUntraced(function* (options) {
5107
5040
  //#endregion
5108
5041
  export { performed as a, pause as i, make as n, mapping as o, viewOf as r, internalsOf as t };
5109
5042
 
5110
- //# sourceMappingURL=page-DHeYWEs-.mjs.map
5043
+ //# sourceMappingURL=page-D-OedyBw.mjs.map