effect-browser 0.2.0-beta.5 → 0.2.0-beta.7
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 +48 -5
- package/dist/{Association-BsSfBYVx.mjs → Association-CH3kPHXE.mjs} +34 -5
- package/dist/Association-CH3kPHXE.mjs.map +1 -0
- package/dist/Browser.d.mts +13 -2
- package/dist/Browser.mjs.map +1 -1
- package/dist/{BrowserData-DPh3Oaof.d.mts → BrowserData-KHk4o__c.d.mts} +4 -4
- package/dist/BrowserData.d.mts +1 -1
- package/dist/BrowserData.mjs +2 -1
- package/dist/BrowserData.mjs.map +1 -1
- package/dist/{BrowserRuntime-DImn_Oe_.mjs → BrowserRuntime-Cr76CN4I.mjs} +195 -33
- package/dist/BrowserRuntime-Cr76CN4I.mjs.map +1 -0
- package/dist/{BrowserRuntime-BBAjpVFa.d.mts → BrowserRuntime-hxkTNlRa.d.mts} +2 -2
- package/dist/BrowserRuntime.d.mts +1 -1
- package/dist/BrowserRuntime.mjs +1 -1
- package/dist/Capture.mjs +1 -1
- package/dist/CaptureData.d.mts +1 -1
- package/dist/Chromium.d.mts +1 -1
- package/dist/Chromium.mjs +1 -1
- package/dist/Errors.d.mts +5 -2
- package/dist/Errors.mjs +10 -1
- package/dist/Errors.mjs.map +1 -1
- package/dist/PageControl.d.mts +1 -1
- package/dist/Testing.d.mts +3 -3
- package/dist/Testing.mjs +28 -14
- package/dist/Testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/Association-BsSfBYVx.mjs.map +0 -1
- package/dist/BrowserRuntime-DImn_Oe_.mjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BrowserError, InitializationError, Reasons } from "./Errors.mjs";
|
|
2
2
|
import { ActionResult, AutomationOptions, BrowserDiagnostic, BrowserDiagnostics, BrowserPolicy, Checkpoint, CheckpointOptions, ClickRequest, ControlFacts, FillFormOptions, FillFormRequest, FillFormResult, FillRequest, FrameInfo, HoverRequest, Identifier, InlineFiles, InputReceipt, KeyStroke, NavigateRequest, NavigationResult, Observation, ObservationOptions, ObservedControl, ObservedElement, PageExecutionState, PageInfo, PageSuspension, PointerMoveRequest, PressRequest, ReadTextRequest, SafeFilename, ScreenshotRequest, ScreenshotResult, ScrollRequest, SelectOptions, Selector, SessionStatus, StartNavigationRequest, Target, TextResult as TextResult$1, TypeRequest, Viewport, WaitForElementRequest, WheelRequest } from "./BrowserData.mjs";
|
|
3
3
|
import { m as snapshot, o as Plan, p as invoke, u as empty } from "./Bootstrap-CeQOWBy6.mjs";
|
|
4
|
-
import { a as NativeFailure, c as
|
|
4
|
+
import { a as NativeFailure, c as providerReason, d as sanitize, f as timeout, i as pngGeometry, l as publicError, o as closeWithin, s as failure, t as associate, u as safeDecode } from "./Association-CH3kPHXE.mjs";
|
|
5
5
|
import { t as associatePageControl } from "./PageControlAssociation-DabEg5AR.mjs";
|
|
6
6
|
import { Cause, Clock, Crypto, Deferred, Duration, Effect, Exit, FiberSet, Option, Redacted, Schema, Scope, Semaphore } from "effect";
|
|
7
7
|
//#region src/internal/browser/Binding.ts
|
|
@@ -24,7 +24,7 @@ const fromNativeAttempt = (attempt) => ({ connect: (request) => Effect.tryPromis
|
|
|
24
24
|
return pending;
|
|
25
25
|
},
|
|
26
26
|
catch: (error) => Schema.is(InitializationError)(error) ? error : publicError(error, "connect", {
|
|
27
|
-
reason:
|
|
27
|
+
reason: providerReason(error),
|
|
28
28
|
outcome: "unknown"
|
|
29
29
|
})
|
|
30
30
|
}) });
|
|
@@ -3525,7 +3525,7 @@ const clientNavigationEmitter = (frame) => {
|
|
|
3525
3525
|
* The connection's pages, frames and document epochs, and the one selected target every other
|
|
3526
3526
|
* seam acts on. A page beyond the configured bound is handed to policy recovery, never tracked.
|
|
3527
3527
|
*/
|
|
3528
|
-
const makeTargets = (browser, context, options, connectionNamespace, closing, hooks) => {
|
|
3528
|
+
const makeTargets = (browser, context, options, connectionNamespace, closing, hooks, windows) => {
|
|
3529
3529
|
const entries = /* @__PURE__ */ new Map();
|
|
3530
3530
|
const byPage = /* @__PURE__ */ new WeakMap();
|
|
3531
3531
|
const frameIds = /* @__PURE__ */ new WeakMap();
|
|
@@ -3534,6 +3534,7 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3534
3534
|
const navigating = /* @__PURE__ */ new Set();
|
|
3535
3535
|
let serial = 0, frameSerial = 0;
|
|
3536
3536
|
let creatingPage = false;
|
|
3537
|
+
const arrivals = /* @__PURE__ */ new Set();
|
|
3537
3538
|
const epochOf = (frame) => documentEpochs.get(frame) ?? 0;
|
|
3538
3539
|
const frameId = (frame) => {
|
|
3539
3540
|
let id = frameIds.get(frame);
|
|
@@ -3552,6 +3553,7 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3552
3553
|
off: []
|
|
3553
3554
|
};
|
|
3554
3555
|
byPage.set(page, entry);
|
|
3556
|
+
for (const arrived of arrivals) arrived(entry);
|
|
3555
3557
|
if (entries.size >= options.maxPages) {
|
|
3556
3558
|
hooks.overflow(entry);
|
|
3557
3559
|
return entry;
|
|
@@ -3657,6 +3659,63 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3657
3659
|
}
|
|
3658
3660
|
};
|
|
3659
3661
|
const urlOf = (frame) => safeDecode(URLText, frame.url());
|
|
3662
|
+
/**
|
|
3663
|
+
* Opens one page: in a window of its own when the browser opens one, else as a tab. The window's
|
|
3664
|
+
* page arrives through the context's page event like any other, and its target id tells it
|
|
3665
|
+
* apart from a popup arriving at the same time. It is registered, and sized, when this returns.
|
|
3666
|
+
*/
|
|
3667
|
+
const open = async (signal) => {
|
|
3668
|
+
const arrived = [];
|
|
3669
|
+
let wake = () => {};
|
|
3670
|
+
const arrival = (entry) => {
|
|
3671
|
+
arrived.push(entry);
|
|
3672
|
+
wake();
|
|
3673
|
+
};
|
|
3674
|
+
const next = () => new Promise((resolve, reject) => {
|
|
3675
|
+
const abort = () => reject(failure(Reasons.Interrupted.make({}), "unknown"));
|
|
3676
|
+
if (signal.aborted) {
|
|
3677
|
+
abort();
|
|
3678
|
+
return;
|
|
3679
|
+
}
|
|
3680
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3681
|
+
wake = () => {
|
|
3682
|
+
signal.removeEventListener("abort", abort);
|
|
3683
|
+
resolve();
|
|
3684
|
+
};
|
|
3685
|
+
});
|
|
3686
|
+
const adopt = async (created) => {
|
|
3687
|
+
for (let index = 0;; index++) {
|
|
3688
|
+
while (arrived.length <= index) await next();
|
|
3689
|
+
const candidate = arrived[index];
|
|
3690
|
+
const id = candidate === void 0 ? void 0 : await targetId(candidate).catch(() => {});
|
|
3691
|
+
if (candidate !== void 0 && id === created) return candidate;
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
/** Every other page that arrived meanwhile is from outside, and its policy applies now. */
|
|
3695
|
+
const settle = (isOurs) => {
|
|
3696
|
+
for (const entry of arrived) if (!isOurs(entry) && entries.get(entry.id) === entry) hooks.external(entry);
|
|
3697
|
+
};
|
|
3698
|
+
arrivals.add(arrival);
|
|
3699
|
+
try {
|
|
3700
|
+
const opened = await windows.open();
|
|
3701
|
+
if (opened === void 0) {
|
|
3702
|
+
const entry = register(await context.newPage());
|
|
3703
|
+
settle((candidate) => candidate === entry);
|
|
3704
|
+
return entry;
|
|
3705
|
+
}
|
|
3706
|
+
try {
|
|
3707
|
+
const [entry] = await Promise.all([adopt(opened.targetId), opened.sized]);
|
|
3708
|
+
settle((candidate) => candidate === entry);
|
|
3709
|
+
return entry;
|
|
3710
|
+
} catch (error) {
|
|
3711
|
+
await closeWithin(opened.discard).catch(() => {});
|
|
3712
|
+
settle((candidate) => candidate.targetId === void 0 || candidate.targetId === opened.targetId);
|
|
3713
|
+
throw error;
|
|
3714
|
+
}
|
|
3715
|
+
} finally {
|
|
3716
|
+
arrivals.delete(arrival);
|
|
3717
|
+
}
|
|
3718
|
+
};
|
|
3660
3719
|
const selectedUrl = () => urlOf(selectedCurrent().frame);
|
|
3661
3720
|
const url = (target) => urlOf(current(target).frame);
|
|
3662
3721
|
/**
|
|
@@ -3667,7 +3726,7 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3667
3726
|
if (options.newPage || entries.size === 0) {
|
|
3668
3727
|
creatingPage = true;
|
|
3669
3728
|
try {
|
|
3670
|
-
selection.entry =
|
|
3729
|
+
selection.entry = await open(AbortSignal.timeout(15e3));
|
|
3671
3730
|
} finally {
|
|
3672
3731
|
creatingPage = false;
|
|
3673
3732
|
}
|
|
@@ -3708,11 +3767,10 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3708
3767
|
selected: selection.entry === entry
|
|
3709
3768
|
});
|
|
3710
3769
|
};
|
|
3770
|
+
/** Each page's title is its own round trip, so they are read together rather than in turn. */
|
|
3711
3771
|
const listPages = (ticket) => sanitize(async () => {
|
|
3712
3772
|
ticket.check();
|
|
3713
|
-
|
|
3714
|
-
for (const entry of entries.values()) output.push(await pageInfo(entry, ticket));
|
|
3715
|
-
return output;
|
|
3773
|
+
return Promise.all([...entries.values()].map((entry) => pageInfo(entry, ticket)));
|
|
3716
3774
|
});
|
|
3717
3775
|
const explicitPage = async (page, ticket) => {
|
|
3718
3776
|
ticket.check();
|
|
@@ -3723,6 +3781,7 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3723
3781
|
if (entries.get(entry.id) !== entry || entry.page.isClosed()) throw failure(Reasons.Stale.make({}), "undispatched");
|
|
3724
3782
|
return entry;
|
|
3725
3783
|
};
|
|
3784
|
+
const describePage = (page, ticket) => sanitize(async () => pageInfo(await explicitPage(page, ticket), ticket));
|
|
3726
3785
|
const resolvePage = (page, ticket) => sanitize(async () => {
|
|
3727
3786
|
const entry = await explicitPage(page, ticket);
|
|
3728
3787
|
return {
|
|
@@ -3748,12 +3807,11 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3748
3807
|
creatingPage = true;
|
|
3749
3808
|
let entry;
|
|
3750
3809
|
try {
|
|
3751
|
-
|
|
3810
|
+
entry = await open(ticket.signal);
|
|
3752
3811
|
if (ticket.signal.aborted) {
|
|
3753
|
-
await closeWithin(() => page.close()).catch(() => {});
|
|
3812
|
+
await closeWithin(() => entry.page.close()).catch(() => {});
|
|
3754
3813
|
ticket.check();
|
|
3755
3814
|
}
|
|
3756
|
-
entry = register(page);
|
|
3757
3815
|
} finally {
|
|
3758
3816
|
creatingPage = false;
|
|
3759
3817
|
}
|
|
@@ -3765,6 +3823,12 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3765
3823
|
await entry.page.close({ runBeforeUnload: false });
|
|
3766
3824
|
ticket.check();
|
|
3767
3825
|
});
|
|
3826
|
+
const containPage = async (pageId) => {
|
|
3827
|
+
const entry = entries.get(pageId);
|
|
3828
|
+
if (entry === void 0) return;
|
|
3829
|
+
await closeWithin(() => entry.page.close({ runBeforeUnload: false }));
|
|
3830
|
+
if (!entry.page.isClosed()) throw failure(Reasons.Failed.make({}));
|
|
3831
|
+
};
|
|
3768
3832
|
const listFrames = (ticket, page) => sanitize(async () => {
|
|
3769
3833
|
ticket.check();
|
|
3770
3834
|
const frames = (page === void 0 ? selectedCurrent().entry : await explicitPage(page, ticket)).page.frames();
|
|
@@ -3824,10 +3888,12 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3824
3888
|
selected,
|
|
3825
3889
|
selectedTargetId,
|
|
3826
3890
|
listPages,
|
|
3891
|
+
describePage,
|
|
3827
3892
|
resolvePage,
|
|
3828
3893
|
selectPage,
|
|
3829
3894
|
newPage,
|
|
3830
3895
|
closePage,
|
|
3896
|
+
containPage,
|
|
3831
3897
|
listFrames,
|
|
3832
3898
|
resolveFrame,
|
|
3833
3899
|
selectFrame
|
|
@@ -3836,6 +3902,12 @@ const makeTargets = (browser, context, options, connectionNamespace, closing, ho
|
|
|
3836
3902
|
//#endregion
|
|
3837
3903
|
//#region src/internal/browser/Playwright.ts
|
|
3838
3904
|
const NativeWindow = Schema.Struct({ windowId: Schema.Natural });
|
|
3905
|
+
const CreatedTarget = Schema.Struct({ targetId: Identifier });
|
|
3906
|
+
/**
|
|
3907
|
+
* Playwright 1.63.0 reports the browser's own error reply to a CDP command this way. A reply is
|
|
3908
|
+
* the browser's answer that it did not do what was asked; anything else leaves that unknown.
|
|
3909
|
+
*/
|
|
3910
|
+
const refusedBy = (method, error) => error instanceof Error && error.message.includes(`Protocol error (${method})`) && !error.message.includes("session closed");
|
|
3839
3911
|
/**
|
|
3840
3912
|
* Connects to an already validated or host-resolved CDP endpoint and builds the one driver. The
|
|
3841
3913
|
* import is lazy, so constructing any Layer cannot load Playwright or allocate a browser. A
|
|
@@ -3862,7 +3934,7 @@ const connectPlaywrightEndpoint = async (endpoint, signal, identity, options, ev
|
|
|
3862
3934
|
return driver;
|
|
3863
3935
|
} catch (error) {
|
|
3864
3936
|
await closeWithin(() => browser.close()).catch(() => {});
|
|
3865
|
-
throw Schema.is(BrowserError)(error) || Schema.is(InitializationError)(error) || Schema.is(NativeFailure)(error) ? error : failure(
|
|
3937
|
+
throw Schema.is(BrowserError)(error) || Schema.is(InitializationError)(error) || Schema.is(NativeFailure)(error) ? error : failure(providerReason(error));
|
|
3866
3938
|
}
|
|
3867
3939
|
};
|
|
3868
3940
|
/** Tests may supply a real already-connected browser. This is deliberately not an exported subpath. */
|
|
@@ -3882,6 +3954,7 @@ const makePlaywrightDriver = async (browser, identity, options, events) => {
|
|
|
3882
3954
|
let closing = false;
|
|
3883
3955
|
let initialized = false;
|
|
3884
3956
|
let browserCdp;
|
|
3957
|
+
let windows = true;
|
|
3885
3958
|
let sameDocumentCapture = () => {};
|
|
3886
3959
|
const targets = makeTargets(browser, context, options, identity.namespace, () => closing, {
|
|
3887
3960
|
opened: (entry, created) => {
|
|
@@ -3895,6 +3968,11 @@ const makePlaywrightDriver = async (browser, identity, options, events) => {
|
|
|
3895
3968
|
if (initialized) initialization.attachPage(entry.page);
|
|
3896
3969
|
if (initialized && !created && options.popupPolicy === "pause") events.pause("popup");
|
|
3897
3970
|
},
|
|
3971
|
+
external: (entry) => {
|
|
3972
|
+
if (!initialized) return;
|
|
3973
|
+
if (options.popupPolicy === "close") policyCleanup.run(entry.page, () => entry.page.close({ runBeforeUnload: false }));
|
|
3974
|
+
else if (options.popupPolicy === "pause") events.pause("popup");
|
|
3975
|
+
},
|
|
3898
3976
|
overflow: (entry) => {
|
|
3899
3977
|
if (options.popupPolicy === "close") policyCleanup.run(entry.page, () => entry.page.close({ runBeforeUnload: false }), { overflow: "popup-overflow" });
|
|
3900
3978
|
else events.fault({
|
|
@@ -3941,7 +4019,29 @@ const makePlaywrightDriver = async (browser, identity, options, events) => {
|
|
|
3941
4019
|
}
|
|
3942
4020
|
},
|
|
3943
4021
|
changed: (reason, scope) => observation.changed(reason, scope)
|
|
3944
|
-
})
|
|
4022
|
+
}, { open: async () => {
|
|
4023
|
+
if (!windows) return void 0;
|
|
4024
|
+
const cdp = browserSession();
|
|
4025
|
+
let created;
|
|
4026
|
+
try {
|
|
4027
|
+
created = await cdp.send("Target.createTarget", {
|
|
4028
|
+
url: "about:blank",
|
|
4029
|
+
newWindow: true
|
|
4030
|
+
});
|
|
4031
|
+
} catch (error) {
|
|
4032
|
+
if (!refusedBy("Target.createTarget", error)) throw error;
|
|
4033
|
+
windows = false;
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
4036
|
+
const { targetId } = safeDecode(CreatedTarget, created);
|
|
4037
|
+
return {
|
|
4038
|
+
targetId,
|
|
4039
|
+
sized: options.preserveViewport ? Promise.resolve() : sizeNativeWindow(targetId),
|
|
4040
|
+
discard: async () => {
|
|
4041
|
+
await cdp.send("Target.closeTarget", { targetId });
|
|
4042
|
+
}
|
|
4043
|
+
};
|
|
4044
|
+
} });
|
|
3945
4045
|
const { current, entries, register } = targets;
|
|
3946
4046
|
const initialization = makeInitialization(context, options, identity.bindings, targets, callbacks, events, () => closing);
|
|
3947
4047
|
const observation = makeObservation(targets, identity.namespace, events);
|
|
@@ -3970,26 +4070,32 @@ const makePlaywrightDriver = async (browser, identity, options, events) => {
|
|
|
3970
4070
|
};
|
|
3971
4071
|
context.on("page", onPage);
|
|
3972
4072
|
browser.on("disconnected", onDisconnected);
|
|
3973
|
-
const
|
|
4073
|
+
const browserSession = () => {
|
|
3974
4074
|
if (browserCdp === void 0) throw failure(Reasons.Closed.make({}), "undispatched");
|
|
3975
|
-
|
|
3976
|
-
|
|
4075
|
+
return browserCdp;
|
|
4076
|
+
};
|
|
4077
|
+
const sizeNativeWindow = async (targetId) => {
|
|
4078
|
+
const cdp = browserSession();
|
|
4079
|
+
const current = await cdp.send("Browser.getWindowForTarget", { targetId });
|
|
3977
4080
|
const native = safeDecode(NativeWindow, current);
|
|
3978
|
-
await
|
|
4081
|
+
await cdp.send("Browser.setContentsSize", {
|
|
3979
4082
|
windowId: native.windowId,
|
|
3980
4083
|
width: options.viewport.width,
|
|
3981
4084
|
height: options.viewport.height
|
|
3982
4085
|
});
|
|
3983
4086
|
};
|
|
4087
|
+
const sizeNativeContents = async (entry) => sizeNativeWindow(await targets.targetId(entry));
|
|
3984
4088
|
const driver = {
|
|
3985
4089
|
...options.pageControl ? { pageControl: pageControl.operations } : {},
|
|
3986
4090
|
selected: targets.selected,
|
|
3987
4091
|
selectedTargetId: targets.selectedTargetId,
|
|
3988
4092
|
listPages: targets.listPages,
|
|
4093
|
+
describePage: targets.describePage,
|
|
3989
4094
|
resolvePage: targets.resolvePage,
|
|
3990
4095
|
selectPage: targets.selectPage,
|
|
3991
4096
|
newPage: targets.newPage,
|
|
3992
4097
|
closePage: targets.closePage,
|
|
4098
|
+
containPage: targets.containPage,
|
|
3993
4099
|
listFrames: targets.listFrames,
|
|
3994
4100
|
resolveFrame: targets.resolveFrame,
|
|
3995
4101
|
selectFrame: targets.selectFrame,
|
|
@@ -4236,6 +4342,7 @@ const makeSession = (controls, bindings) => {
|
|
|
4236
4342
|
kind: "type"
|
|
4237
4343
|
}))),
|
|
4238
4344
|
pages: controls.pages,
|
|
4345
|
+
describePage: (page) => checked(PageInfo, page, "describe-page").pipe(Effect.flatMap(controls.describePage)),
|
|
4239
4346
|
frames: controls.frames,
|
|
4240
4347
|
framesOf: (page) => checked(PageInfo, page, "list-frames").pipe(Effect.flatMap(controls.framesOf)),
|
|
4241
4348
|
pinPage: (page) => checked(PageInfo, page, "target").pipe(Effect.flatMap(controls.pinPage), Effect.map(makePinnedTarget)),
|
|
@@ -4526,6 +4633,7 @@ const makeOwner = Effect.fnUntraced(function* (limits) {
|
|
|
4526
4633
|
};
|
|
4527
4634
|
const guard = (operation, body, options = {}) => Effect.suspend(() => {
|
|
4528
4635
|
let admitted;
|
|
4636
|
+
const waitUntil = options.waitUntil ?? (options.queue === true ? Number(clock.monotonicTimeNanosUnsafe()) / 1e6 + Math.min(options.timeoutMillis ?? limits.actionTimeoutMillis, limits.actionTimeoutMillis) : void 0);
|
|
4529
4637
|
const work = Effect.gen(function* () {
|
|
4530
4638
|
holdingPermit = true;
|
|
4531
4639
|
if (!(options.phases ?? ["open"]).includes(state.phase) || policies.size > 0) return yield* BrowserError.make({
|
|
@@ -4543,7 +4651,7 @@ const makeOwner = Effect.fnUntraced(function* (limits) {
|
|
|
4543
4651
|
outcome: "undispatched"
|
|
4544
4652
|
});
|
|
4545
4653
|
}
|
|
4546
|
-
const deadline = Math.min(
|
|
4654
|
+
const deadline = Math.min(waitUntil ?? now + limits.actionTimeoutMillis, options.timeoutMillis === void 0 ? Number.POSITIVE_INFINITY : now + options.timeoutMillis, lifetimeDeadline);
|
|
4547
4655
|
if (now >= deadline) return yield* BrowserError.make({
|
|
4548
4656
|
operation,
|
|
4549
4657
|
reason: Reasons.Timeout.make({}),
|
|
@@ -4600,14 +4708,42 @@ const makeOwner = Effect.fnUntraced(function* (limits) {
|
|
|
4600
4708
|
}
|
|
4601
4709
|
};
|
|
4602
4710
|
admitted = ticket;
|
|
4603
|
-
|
|
4711
|
+
let abandoned = false;
|
|
4712
|
+
const abandon = () => {
|
|
4604
4713
|
if (Number(clock.monotonicTimeNanosUnsafe()) / 1e6 >= lifetimeDeadline) expire();
|
|
4605
4714
|
const fenced = controller.signal.aborted;
|
|
4606
4715
|
controller.abort();
|
|
4607
|
-
if (dispatched && !fenced && state.phase !== "closing" && state.phase !== "closed")
|
|
4716
|
+
if (dispatched && !fenced && state.phase !== "closing" && state.phase !== "closed") abandoned = true;
|
|
4608
4717
|
};
|
|
4718
|
+
const current = () => state.generation === generation && state.phase === "open";
|
|
4719
|
+
/**
|
|
4720
|
+
* Work given up after dispatch fences the owner, since nothing knows what it did or
|
|
4721
|
+
* whether more of it will land. An operation confined to one page it may close instead
|
|
4722
|
+
* closes that page: once the browser confirms it closed, nothing still bound for it can
|
|
4723
|
+
* land anywhere, and every other page keeps working. It is still never replayed.
|
|
4724
|
+
*/
|
|
4725
|
+
const settle = Effect.uninterruptible(Effect.suspend(() => {
|
|
4726
|
+
if (!abandoned) return Effect.void;
|
|
4727
|
+
abandoned = false;
|
|
4728
|
+
const page = current() ? options.contain?.() : void 0;
|
|
4729
|
+
if (page === void 0) return Effect.sync(() => fence("uncertain", "uncertain"));
|
|
4730
|
+
return page.close.pipe(Effect.map((closed) => {
|
|
4731
|
+
if (!closed || !current()) {
|
|
4732
|
+
fence("uncertain", "uncertain");
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4735
|
+
unresolved.delete(controller);
|
|
4736
|
+
const reservation = reservations.get(page.pageId);
|
|
4737
|
+
if (reservation !== void 0) {
|
|
4738
|
+
reservations.delete(page.pageId);
|
|
4739
|
+
unresolved.delete(reservation);
|
|
4740
|
+
reservation.abort();
|
|
4741
|
+
}
|
|
4742
|
+
record("page-contained", "confirmed");
|
|
4743
|
+
}));
|
|
4744
|
+
}));
|
|
4609
4745
|
return yield* within(body(ticket), ticket.deadline, () => {
|
|
4610
|
-
|
|
4746
|
+
abandon();
|
|
4611
4747
|
return BrowserError.make({
|
|
4612
4748
|
operation,
|
|
4613
4749
|
reason: Reasons.Timeout.make({}),
|
|
@@ -4621,21 +4757,21 @@ const makeOwner = Effect.fnUntraced(function* (limits) {
|
|
|
4621
4757
|
outcome: dispatched ? "unknown" : "undispatched"
|
|
4622
4758
|
})
|
|
4623
4759
|
})), Effect.catch((error) => {
|
|
4624
|
-
if (dispatched)
|
|
4625
|
-
|
|
4626
|
-
return Effect.fail(BrowserError.make({
|
|
4760
|
+
if (dispatched) abandon();
|
|
4761
|
+
const failure = Schema.is(BrowserError)(error) ? Effect.fail(BrowserError.make({
|
|
4627
4762
|
operation,
|
|
4628
4763
|
reason: error.reason,
|
|
4629
4764
|
outcome: error.outcome
|
|
4630
|
-
}));
|
|
4631
|
-
|
|
4765
|
+
})) : Effect.fail(error);
|
|
4766
|
+
return settle.pipe(Effect.andThen(failure));
|
|
4767
|
+
}), Effect.onInterrupt(() => Effect.sync(abandon).pipe(Effect.andThen(settle))), Effect.tap(() => Effect.sync(() => unresolved.delete(controller))), Effect.ensuring(Effect.sync(() => {
|
|
4632
4768
|
controller.abort();
|
|
4633
4769
|
if (active === controller) active = void 0;
|
|
4634
4770
|
})));
|
|
4635
4771
|
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
4636
4772
|
holdingPermit = false;
|
|
4637
4773
|
})));
|
|
4638
|
-
if (
|
|
4774
|
+
if (waitUntil !== void 0) return within(semaphore.withPermits(1)(work), Math.min(waitUntil, lifetimeDeadline), () => BrowserError.make({
|
|
4639
4775
|
operation,
|
|
4640
4776
|
reason: Reasons.Timeout.make({}),
|
|
4641
4777
|
outcome: admitted?.dispatched === true ? "unknown" : "undispatched"
|
|
@@ -4714,8 +4850,8 @@ const makeOwner = Effect.fnUntraced(function* (limits) {
|
|
|
4714
4850
|
};
|
|
4715
4851
|
});
|
|
4716
4852
|
/** Without a native answer, whether the step was sent is all the owner knows about it. */
|
|
4717
|
-
const unsettled = (ticket) => ({
|
|
4718
|
-
reason:
|
|
4853
|
+
const unsettled = (ticket, error) => ({
|
|
4854
|
+
reason: providerReason(error),
|
|
4719
4855
|
outcome: ticket.dispatched ? "unknown" : "undispatched"
|
|
4720
4856
|
});
|
|
4721
4857
|
/**
|
|
@@ -4748,12 +4884,12 @@ const native = (operation, ticket, body) => Effect.callback((resume) => {
|
|
|
4748
4884
|
}, (error) => {
|
|
4749
4885
|
if (!done) {
|
|
4750
4886
|
cleanup();
|
|
4751
|
-
resume(Effect.fail(publicError(error, operation, unsettled(ticket))));
|
|
4887
|
+
resume(Effect.fail(publicError(error, operation, unsettled(ticket, error))));
|
|
4752
4888
|
}
|
|
4753
4889
|
});
|
|
4754
4890
|
} catch (error) {
|
|
4755
4891
|
cleanup();
|
|
4756
|
-
resume(Effect.fail(publicError(error, operation, unsettled(ticket))));
|
|
4892
|
+
resume(Effect.fail(publicError(error, operation, unsettled(ticket, error))));
|
|
4757
4893
|
}
|
|
4758
4894
|
return Effect.sync(cleanup);
|
|
4759
4895
|
});
|
|
@@ -5165,6 +5301,27 @@ const acquireSession = Effect.fnUntraced(function* (limits, options) {
|
|
|
5165
5301
|
outcome: "undispatched"
|
|
5166
5302
|
})) : Effect.void;
|
|
5167
5303
|
});
|
|
5304
|
+
/**
|
|
5305
|
+
* A page other than the selected one can take an unknown outcome of its own mutation with it:
|
|
5306
|
+
* closing it ends everything still bound for it, where the selected page and every other one
|
|
5307
|
+
* go on working. The selected page's own outcome still fences, since direct operations follow
|
|
5308
|
+
* it and nothing could be selected in its place.
|
|
5309
|
+
*/
|
|
5310
|
+
const containment = (pageId) => {
|
|
5311
|
+
const current = driver;
|
|
5312
|
+
if (current === void 0) return void 0;
|
|
5313
|
+
let selected;
|
|
5314
|
+
try {
|
|
5315
|
+
selected = current.selected().pageId;
|
|
5316
|
+
} catch {
|
|
5317
|
+
return;
|
|
5318
|
+
}
|
|
5319
|
+
if (selected === pageId) return void 0;
|
|
5320
|
+
return {
|
|
5321
|
+
pageId,
|
|
5322
|
+
close: Effect.promise(() => current.containPage(pageId).then(() => true, () => false))
|
|
5323
|
+
};
|
|
5324
|
+
};
|
|
5168
5325
|
const waitFree = (operation, pageId) => Effect.suspend(() => owner.waitPending(pageId) ? Effect.fail(BrowserError.make({
|
|
5169
5326
|
operation,
|
|
5170
5327
|
reason: Reasons.Busy.make({}),
|
|
@@ -5257,7 +5414,8 @@ const acquireSession = Effect.fnUntraced(function* (limits, options) {
|
|
|
5257
5414
|
}), {
|
|
5258
5415
|
mutation,
|
|
5259
5416
|
mutationScope: () => ({ pageId: browserTarget?.pageId ?? getDriver().selected().pageId }),
|
|
5260
|
-
preflight: mutation ? Effect.suspend(check).pipe(Effect.andThen(unreserved(operation, browserTarget))) : Effect.suspend(check)
|
|
5417
|
+
preflight: mutation ? Effect.suspend(check).pipe(Effect.andThen(unreserved(operation, browserTarget))) : Effect.suspend(check),
|
|
5418
|
+
...browserTarget === void 0 ? {} : { contain: () => containment(browserTarget.pageId) }
|
|
5261
5419
|
});
|
|
5262
5420
|
const operationTarget = () => retained?.target === void 0 ? capture.target() : Target.make({
|
|
5263
5421
|
generation: retained.generation,
|
|
@@ -5322,6 +5480,7 @@ const acquireSession = Effect.fnUntraced(function* (limits, options) {
|
|
|
5322
5480
|
});
|
|
5323
5481
|
const loadingTimeout = Math.min(timeoutMillis, remainingLifetime);
|
|
5324
5482
|
const navigation = await driver.beginNavigation(url, loadingTimeout, ticket, browserTarget, control);
|
|
5483
|
+
ticket.check();
|
|
5325
5484
|
return {
|
|
5326
5485
|
target,
|
|
5327
5486
|
navigation,
|
|
@@ -5623,6 +5782,7 @@ const acquireSession = Effect.fnUntraced(function* (limits, options) {
|
|
|
5623
5782
|
revalidate: (reference) => nativeOperation("revalidate", (driver, ticket) => driver.revalidate(reference, ticket), { charge: false }),
|
|
5624
5783
|
readiness: owner.guard("ready", (ticket) => native("ready", ticket, () => getDriver().documentReadiness(ticket)), { charge: false }),
|
|
5625
5784
|
pages: nativeOperation("list-pages", (driver, ticket) => driver.listPages(ticket), { charge: false }),
|
|
5785
|
+
describePage: (page) => nativeOperation("describe-page", (driver, ticket) => driver.describePage(page, ticket), { charge: false }),
|
|
5626
5786
|
frames: nativeOperation("list-frames", (driver, ticket) => driver.listFrames(ticket), { charge: false }),
|
|
5627
5787
|
framesOf: (page) => nativeOperation("list-frames", (driver, ticket) => driver.listFrames(ticket, page), { charge: false }),
|
|
5628
5788
|
pinPage: (page) => pinned((driver, ticket) => driver.resolvePage(page, ticket)),
|
|
@@ -5635,11 +5795,13 @@ const acquireSession = Effect.fnUntraced(function* (limits, options) {
|
|
|
5635
5795
|
await driver.selectFrame(id, ticket);
|
|
5636
5796
|
owner.state.selection++;
|
|
5637
5797
|
}, { charge: false }),
|
|
5798
|
+
/** No other page's work conflicts with opening one, so it waits its turn rather than refusing. */
|
|
5638
5799
|
createPage: () => nativeOperation("new-page", (driver, ticket) => driver.newPage(ticket), {
|
|
5639
5800
|
mutation: true,
|
|
5640
5801
|
mutationScope: () => "none",
|
|
5641
5802
|
charge: false,
|
|
5642
|
-
anyPage: true
|
|
5803
|
+
anyPage: true,
|
|
5804
|
+
queue: true
|
|
5643
5805
|
}),
|
|
5644
5806
|
closePage: (page) => nativeOperation("close-page", (driver, ticket) => driver.closePage(page, ticket), {
|
|
5645
5807
|
mutation: true,
|
|
@@ -5895,4 +6057,4 @@ const make = Effect.fnUntraced(function* (options) {
|
|
|
5895
6057
|
//#endregion
|
|
5896
6058
|
export { cleanupStep as a, checked as c, randomUuid as i, fromNativeAttempt as l, make as n, noConnection as o, playwright as r, reported as s, DownloadEvent as t, issueBinding as u };
|
|
5897
6059
|
|
|
5898
|
-
//# sourceMappingURL=BrowserRuntime-
|
|
6060
|
+
//# sourceMappingURL=BrowserRuntime-Cr76CN4I.mjs.map
|