effect-browser 0.2.0-beta.9 → 0.3.0-beta.10

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 (204) hide show
  1. package/README.md +849 -1161
  2. package/dist/Browser-S4kvpmqa.mjs +605 -0
  3. package/dist/Browser-S4kvpmqa.mjs.map +1 -0
  4. package/dist/Browser.d.mts +127 -3
  5. package/dist/Browser.mjs +2 -22
  6. package/dist/BrowserError-Coy7EW1N.d.mts +140 -0
  7. package/dist/BrowserError.d.mts +2 -0
  8. package/dist/BrowserError.mjs +211 -0
  9. package/dist/BrowserError.mjs.map +1 -0
  10. package/dist/BrowserEvent-BRu2juEz.d.mts +279 -0
  11. package/dist/BrowserEvent.d.mts +2 -0
  12. package/dist/BrowserEvent.mjs +297 -0
  13. package/dist/BrowserEvent.mjs.map +1 -0
  14. package/dist/Cdp.d.mts +32 -0
  15. package/dist/Cdp.mjs +61 -0
  16. package/dist/Cdp.mjs.map +1 -0
  17. package/dist/Change-DNvztNLu.mjs +138 -0
  18. package/dist/Change-DNvztNLu.mjs.map +1 -0
  19. package/dist/Change-iMlcuzVD.d.mts +71 -0
  20. package/dist/Change.d.mts +2 -0
  21. package/dist/Change.mjs +2 -0
  22. package/dist/Chromium.d.mts +29 -81
  23. package/dist/Chromium.mjs +57 -392
  24. package/dist/Chromium.mjs.map +1 -1
  25. package/dist/Expect.d.mts +62 -0
  26. package/dist/Expect.mjs +114 -0
  27. package/dist/Expect.mjs.map +1 -0
  28. package/dist/Frame-BERmf_gM.d.mts +117 -0
  29. package/dist/Frame.d.mts +2 -0
  30. package/dist/Frame.mjs +123 -0
  31. package/dist/Frame.mjs.map +1 -0
  32. package/dist/Moment.d.mts +2 -0
  33. package/dist/Moment.mjs +288 -0
  34. package/dist/Moment.mjs.map +1 -0
  35. package/dist/Motion.d.mts +40 -0
  36. package/dist/Motion.mjs +122 -0
  37. package/dist/Motion.mjs.map +1 -0
  38. package/dist/Page-B5cjaUp5.mjs +618 -0
  39. package/dist/Page-B5cjaUp5.mjs.map +1 -0
  40. package/dist/Page-BlPjfibv.d.mts +595 -0
  41. package/dist/Page.d.mts +2 -0
  42. package/dist/Page.mjs +2 -0
  43. package/dist/Plan-BI8XGvDu.d.mts +209 -0
  44. package/dist/Plan-D1wrJVs8.mjs +508 -0
  45. package/dist/Plan-D1wrJVs8.mjs.map +1 -0
  46. package/dist/Plan.d.mts +2 -3
  47. package/dist/Plan.mjs +2 -2
  48. package/dist/Policy.d.mts +76 -0
  49. package/dist/Policy.mjs +237 -0
  50. package/dist/Policy.mjs.map +1 -0
  51. package/dist/Presentation.d.mts +48 -0
  52. package/dist/Presentation.mjs +117 -0
  53. package/dist/Presentation.mjs.map +1 -0
  54. package/dist/Snapshot-B34hZ4Jl.d.mts +68 -0
  55. package/dist/Snapshot.d.mts +2 -0
  56. package/dist/Snapshot.mjs +79 -0
  57. package/dist/Snapshot.mjs.map +1 -0
  58. package/dist/Stage.d.mts +64 -0
  59. package/dist/Stage.mjs +237 -0
  60. package/dist/Stage.mjs.map +1 -0
  61. package/dist/Supervisor-CYWFK3vM.d.mts +280 -0
  62. package/dist/Supervisor-TNKNOVzu.mjs +599 -0
  63. package/dist/Supervisor-TNKNOVzu.mjs.map +1 -0
  64. package/dist/Supervisor.d.mts +2 -0
  65. package/dist/Supervisor.mjs +2 -0
  66. package/dist/context-B6xr8nVi.mjs +261 -0
  67. package/dist/context-B6xr8nVi.mjs.map +1 -0
  68. package/dist/index.d.mts +17 -14
  69. package/dist/index.mjs +18 -13
  70. package/dist/match.inpage-xOe9LNO0.mjs +48 -0
  71. package/dist/match.inpage-xOe9LNO0.mjs.map +1 -0
  72. package/dist/page-B8tUQELg.mjs +5073 -0
  73. package/dist/page-B8tUQELg.mjs.map +1 -0
  74. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  75. package/dist/url-Cd04Okid.mjs +264 -0
  76. package/dist/url-Cd04Okid.mjs.map +1 -0
  77. package/package.json +96 -61
  78. package/src/Browser.ts +666 -0
  79. package/src/BrowserError.ts +234 -0
  80. package/src/BrowserEvent.ts +296 -0
  81. package/src/Cdp.ts +101 -0
  82. package/src/Change.ts +88 -0
  83. package/src/Chromium.ts +102 -0
  84. package/src/Expect.ts +180 -0
  85. package/src/Frame.ts +130 -0
  86. package/src/Moment.ts +479 -0
  87. package/src/Motion.ts +166 -0
  88. package/src/Page.ts +522 -0
  89. package/src/Plan.ts +663 -0
  90. package/src/Policy.ts +325 -0
  91. package/src/Presentation.ts +195 -0
  92. package/src/Snapshot.ts +89 -0
  93. package/src/Stage.ts +467 -0
  94. package/src/Supervisor.ts +599 -0
  95. package/src/index.ts +20 -0
  96. package/src/internal/input/actions.ts +610 -0
  97. package/src/internal/input/dispatch.ts +161 -0
  98. package/src/internal/input/edit.inpage.ts +162 -0
  99. package/src/internal/input/evidence.inpage.ts +164 -0
  100. package/src/internal/input/gate.ts +224 -0
  101. package/src/internal/input/guard.inpage.ts +605 -0
  102. package/src/internal/input/guard.ts +184 -0
  103. package/src/internal/input/keyboard.ts +112 -0
  104. package/src/internal/input/keys.ts +127 -0
  105. package/src/internal/input/perform.ts +376 -0
  106. package/src/internal/input/pointer.ts +153 -0
  107. package/src/internal/input/replies.ts +195 -0
  108. package/src/internal/input/settle.ts +50 -0
  109. package/src/internal/input/style.ts +186 -0
  110. package/src/internal/input/targets.inpage.ts +293 -0
  111. package/src/internal/input/targets.ts +148 -0
  112. package/src/internal/page/bridge.ts +386 -0
  113. package/src/internal/page/context.ts +312 -0
  114. package/src/internal/page/lane.ts +311 -0
  115. package/src/internal/page/navigation.ts +183 -0
  116. package/src/internal/page/openings.ts +87 -0
  117. package/src/internal/page/page.ts +165 -0
  118. package/src/internal/page/url.ts +100 -0
  119. package/src/internal/page/viewport.ts +43 -0
  120. package/src/internal/pictures/capture.ts +776 -0
  121. package/src/internal/pictures/clock.ts +141 -0
  122. package/src/internal/pictures/counts.ts +92 -0
  123. package/src/internal/pictures/pictures.ts +520 -0
  124. package/src/internal/pictures/transport.ts +242 -0
  125. package/src/internal/reading/choose.ts +100 -0
  126. package/src/internal/reading/context.inpage.ts +201 -0
  127. package/src/internal/reading/controls.inpage.ts +158 -0
  128. package/src/internal/reading/expect.inpage.ts +70 -0
  129. package/src/internal/reading/match.inpage.ts +92 -0
  130. package/src/internal/reading/names.inpage.ts +306 -0
  131. package/src/internal/reading/outline.inpage.ts +260 -0
  132. package/src/internal/reading/reading.ts +223 -0
  133. package/src/internal/reading/ready.inpage.ts +164 -0
  134. package/src/internal/reading/ready.ts +111 -0
  135. package/src/internal/reading/subjects.inpage.ts +188 -0
  136. package/src/internal/reading/text.inpage.ts +135 -0
  137. package/src/internal/reading/walk.inpage.ts +220 -0
  138. package/src/internal/supervisor/lifecycle.ts +416 -0
  139. package/src/internal/timeline/changes.inpage.ts +216 -0
  140. package/src/internal/timeline/changes.ts +100 -0
  141. package/src/internal/timeline/events.ts +94 -0
  142. package/src/internal/timeline/fold.inpage.ts +123 -0
  143. package/src/internal/timeline/history.inpage.ts +229 -0
  144. package/src/internal/timeline/marks.inpage.ts +207 -0
  145. package/src/internal/timeline/record.inpage.ts +133 -0
  146. package/src/internal/timeline/sight.inpage.ts +122 -0
  147. package/src/internal/timeline/window.ts +96 -0
  148. package/src/internal/timeline/words.ts +65 -0
  149. package/dist/Bootstrap-B9UUXsvF.d.mts +0 -183
  150. package/dist/Bootstrap-CeQOWBy6.mjs +0 -189
  151. package/dist/Bootstrap-CeQOWBy6.mjs.map +0 -1
  152. package/dist/Bootstrap.d.mts +0 -3
  153. package/dist/Bootstrap.mjs +0 -2
  154. package/dist/Browser-BF9_sORP.d.mts +0 -388
  155. package/dist/Browser.mjs.map +0 -1
  156. package/dist/BrowserData-CPIIIHol.d.mts +0 -585
  157. package/dist/BrowserData.d.mts +0 -3
  158. package/dist/BrowserData.mjs +0 -695
  159. package/dist/BrowserData.mjs.map +0 -1
  160. package/dist/BrowserRuntime-B6nT1G4X.d.mts +0 -148
  161. package/dist/BrowserRuntime-C1r2aSRK.mjs +0 -6820
  162. package/dist/BrowserRuntime-C1r2aSRK.mjs.map +0 -1
  163. package/dist/BrowserRuntime.d.mts +0 -3
  164. package/dist/BrowserRuntime.mjs +0 -3
  165. package/dist/Capture.d.mts +0 -56
  166. package/dist/Capture.mjs +0 -678
  167. package/dist/Capture.mjs.map +0 -1
  168. package/dist/CaptureData-CYriZHX1.mjs +0 -201
  169. package/dist/CaptureData-CYriZHX1.mjs.map +0 -1
  170. package/dist/CaptureData.d.mts +0 -219
  171. package/dist/CaptureData.mjs +0 -2
  172. package/dist/Errors.d.mts +0 -158
  173. package/dist/Errors.mjs +0 -207
  174. package/dist/Errors.mjs.map +0 -1
  175. package/dist/Images-yCG9BmRU.mjs +0 -76
  176. package/dist/Images-yCG9BmRU.mjs.map +0 -1
  177. package/dist/OperationOptions-DAeRS6O9.mjs +0 -55
  178. package/dist/OperationOptions-DAeRS6O9.mjs.map +0 -1
  179. package/dist/PageControl.d.mts +0 -15
  180. package/dist/PageControl.mjs +0 -34
  181. package/dist/PageControl.mjs.map +0 -1
  182. package/dist/PageRegistry-B3q9DS3g.mjs +0 -164
  183. package/dist/PageRegistry-B3q9DS3g.mjs.map +0 -1
  184. package/dist/Plan-Dyxu2q8C.mjs +0 -543
  185. package/dist/Plan-Dyxu2q8C.mjs.map +0 -1
  186. package/dist/PlanData-B668RxbQ.d.mts +0 -4831
  187. package/dist/PlanData.d.mts +0 -3
  188. package/dist/PlanData.mjs +0 -268
  189. package/dist/PlanData.mjs.map +0 -1
  190. package/dist/PlanInput-BJcPPncs.mjs +0 -75
  191. package/dist/PlanInput-BJcPPncs.mjs.map +0 -1
  192. package/dist/PublicSession-DCSMMqOj.mjs +0 -5602
  193. package/dist/PublicSession-DCSMMqOj.mjs.map +0 -1
  194. package/dist/Testing.d.mts +0 -479
  195. package/dist/Testing.mjs +0 -2147
  196. package/dist/Testing.mjs.map +0 -1
  197. package/dist/Timeline-fV22Xk-S.d.mts +0 -29
  198. package/dist/Timeline.d.mts +0 -4
  199. package/dist/Timeline.mjs +0 -20
  200. package/dist/Timeline.mjs.map +0 -1
  201. package/dist/TimelineData-BsOn-YGZ.d.mts +0 -3818
  202. package/dist/TimelineData.d.mts +0 -3
  203. package/dist/TimelineData.mjs +0 -482
  204. package/dist/TimelineData.mjs.map +0 -1
@@ -0,0 +1,618 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { Box, PageLoaded, Subject, SubjectContext } from "./BrowserEvent.mjs";
3
+ import { c as FailFast, o as withheld, s as Correlation } from "./url-Cd04Okid.mjs";
4
+ import { Frame, Image } from "./Frame.mjs";
5
+ import { Effect, Schema } from "effect";
6
+ //#region src/internal/input/evidence.inpage.ts
7
+ /**
8
+ * In the page: what the page says about an input's first target, for a judge, and which fields a
9
+ * form would send. It never reads what a field holds. See `reading/names.inpage.ts` for what a page-side part may use.
10
+ */
11
+ const evidence = (names, placing) => {
12
+ const { clean, isDocument, isInput, isSelect, isTextArea, nameOf, parentOf, roleOf, textOf } = names;
13
+ const { groups, headingBefore, textBeside, treeOf } = placing;
14
+ /** A field a submission would send. */
15
+ const isField = (element) => (isInput(element) && ![
16
+ "button",
17
+ "submit",
18
+ "reset",
19
+ "image"
20
+ ].includes(element.type) || isSelect(element) || isTextArea(element)) && !element.matches(":disabled");
21
+ const isFilled = (field) => isInput(field) && (field.type === "checkbox" || field.type === "radio") ? field.checked : isInput(field) && field.type === "file" ? (field.files?.length ?? 0) > 0 : field.value !== "";
22
+ const dialogOf = (element) => {
23
+ for (let node = element; node !== null; node = parentOf(node)) if (node.tagName === "DIALOG" || /^(?:alert)?dialog$/.test(node.getAttribute("role") ?? "")) return node;
24
+ };
25
+ /** What the page says about an input's first target, for a judge. Never a field's value. */
26
+ const evidenceOf = (element, form, method, action) => {
27
+ const name = nameOf(element, roleOf(element));
28
+ const ids = element.getAttribute("aria-describedby");
29
+ const description = clean(ids === null ? element.getAttribute("aria-description") ?? element.getAttribute("title") : ids.split(/\s+/).map((id) => element.ownerDocument.getElementById(id)?.textContent ?? "").join(" "), 120);
30
+ const dialog = dialogOf(element);
31
+ const title = dialog?.querySelector("h1,h2,h3,h4,h5,h6,[role=heading]");
32
+ const dialogName = dialog === void 0 ? "" : clean(nameOf(dialog, "dialog"), 120) || (title === null || title === void 0 ? "" : clean(textOf(title), 120));
33
+ const root = element.getRootNode();
34
+ const top = isDocument(root) ? root.body ?? root : root;
35
+ const inTree = element.closest("dialog,[role=dialog],[role=alertdialog]");
36
+ let nearby = "";
37
+ for (const scope of /* @__PURE__ */ new Set([
38
+ element.closest(groups),
39
+ inTree,
40
+ top
41
+ ])) if (scope !== null && nearby === "") nearby = textBeside(element, scope, true) || textBeside(element, scope, false);
42
+ const heading = headingBefore(element, treeOf(element));
43
+ const fields = form === null ? [] : Array.from(form.elements).filter(isField).slice(0, 16).map((field) => {
44
+ const autocomplete = clean(field.getAttribute("autocomplete"), 40);
45
+ return {
46
+ type: isInput(field) ? field.type : field.tagName.toLowerCase(),
47
+ name: clean(isInput(field) && field.type === "hidden" ? field.getAttribute("name") : nameOf(field, roleOf(field)), 40),
48
+ ...autocomplete === "" ? {} : { autocomplete },
49
+ filled: isFilled(field)
50
+ };
51
+ });
52
+ return {
53
+ title: clean(document.title, 120),
54
+ ...description === "" || description === name ? {} : { description },
55
+ ...dialogName === "" ? {} : { dialog: dialogName },
56
+ ...heading === void 0 ? {} : { heading },
57
+ ...nearby === "" ? {} : { nearby },
58
+ ...form === null ? {} : { form: {
59
+ method: method ?? form.method,
60
+ action: action ?? form.action,
61
+ fields
62
+ } }
63
+ };
64
+ };
65
+ return {
66
+ evidenceOf,
67
+ isField,
68
+ isFilled
69
+ };
70
+ };
71
+ const FormFieldSchema = Schema.Struct({
72
+ type: Schema.String,
73
+ name: Schema.String,
74
+ autocomplete: Schema.optional(Schema.String),
75
+ filled: Schema.Boolean
76
+ });
77
+ const EvidenceSchema = Schema.Struct({
78
+ title: Schema.String,
79
+ description: Schema.optional(Schema.String),
80
+ dialog: Schema.optional(Schema.String),
81
+ heading: Schema.optional(Schema.String),
82
+ nearby: Schema.optional(Schema.String),
83
+ form: Schema.optional(Schema.Struct({
84
+ method: Schema.String,
85
+ action: Schema.String,
86
+ fields: Schema.Array(FormFieldSchema)
87
+ }))
88
+ });
89
+ //#endregion
90
+ //#region src/internal/input/guard.inpage.ts
91
+ /**
92
+ * In the page: what the input guard decides on. It inspects an input's targets for the facts a
93
+ * policy weighs, such as a form submission or a secret field, binds an approval to them, and
94
+ * checks before each press that the approved target would still receive it. See `reading/names.inpage.ts` for what a page-side part may use.
95
+ */
96
+ const Fact$1 = Schema.Literals([
97
+ "form-submit",
98
+ "cross-origin",
99
+ "download",
100
+ "upload",
101
+ "secret",
102
+ "scripted",
103
+ "opaque"
104
+ ]);
105
+ const guard = (names, targets, evidence) => {
106
+ const { activeElement, clean, describe, inCurrentDocument, interactiveRoles, isButton, isDisabled, isHtml, isInput, isLabel, isRoot, isSecret, isSelect, isTextArea, lookup, parentOf, refFor, roleOf } = names;
107
+ const { controlOf, details, hitAt, hrefAttribute, linkOf, receives, within } = targets;
108
+ const { evidenceOf, isField, isFilled } = evidence;
109
+ /**
110
+ * The element whose activation behaviour a click or key on `element` runs, as the browser
111
+ * resolves it along the composed path: a link, button, input, summary or a label's control.
112
+ */
113
+ const activationTarget = (element) => {
114
+ for (let node = element; node !== null; node = parentOf(node)) {
115
+ if (hrefAttribute(node) !== null || node.tagName === "BUTTON" || node.tagName === "SUMMARY" || isInput(node) && node.type !== "hidden") return node;
116
+ if (isLabel(node) && node.control !== null) return node.control;
117
+ }
118
+ };
119
+ /**
120
+ * A control between a press's hit and the approved target that is not the target's own
121
+ * activation, such as a link or button a hover handler nested into it. The approval inspected
122
+ * the target, so that control would act unapproved.
123
+ */
124
+ const nestedControl = (element, hit) => {
125
+ if (!within(element, hit)) return void 0;
126
+ const own = activationTarget(element);
127
+ for (let node = hit; node !== null && node !== element; node = parentOf(node) ?? node.ownerDocument.defaultView?.frameElement ?? null) if (node !== own && (hrefAttribute(node) !== null || node.tagName === "BUTTON" || node.tagName === "SUMMARY" || isInput(node) && node.type !== "hidden" || isSelect(node) || isTextArea(node) || isLabel(node) && node.control !== null && node.control !== own || activatingRoles.has(roleOf(node) ?? ""))) return node;
128
+ };
129
+ const textEntry = (element) => isInput(element) && ![
130
+ "button",
131
+ "reset",
132
+ "submit",
133
+ "image",
134
+ "checkbox",
135
+ "radio",
136
+ "file",
137
+ "range",
138
+ "color",
139
+ "hidden"
140
+ ].includes(element.type) || isTextArea(element) || isHtml(element) && element.isContentEditable;
141
+ const activatingRoles = /* @__PURE__ */ new Set([
142
+ "button",
143
+ "link",
144
+ "checkbox",
145
+ "radio",
146
+ "switch",
147
+ "tab",
148
+ "menuitem",
149
+ "menuitemcheckbox",
150
+ "menuitemradio",
151
+ "option",
152
+ "treeitem"
153
+ ]);
154
+ const typingRefusal = (element, explicit) => textEntry(element) ? void 0 : explicit ? `${describe(element)} is not a text field` : activationTarget(element) !== void 0 || isSelect(element) || activatingRoles.has(roleOf(element) ?? "") ? `focus is on ${describe(element)}, which typed text could activate or change; type into a text field by ref, or use press for keys` : void 0;
155
+ const isSubmitter = (element) => isButton(element) && element.type === "submit" || isInput(element) && (element.type === "submit" || element.type === "image");
156
+ const defaultSubmitter = (form) => {
157
+ const root = form.getRootNode();
158
+ return isRoot(root) ? Array.from(root.querySelectorAll("button,input")).find((candidate) => isSubmitter(candidate) && candidate.form === form) : void 0;
159
+ };
160
+ const purpose = (control, form) => [
161
+ control.getAttribute("id"),
162
+ control.getAttribute("name"),
163
+ control.getAttribute("inputmode"),
164
+ control.getAttribute("autocomplete"),
165
+ control.getAttribute("aria-label"),
166
+ form?.getAttribute("id"),
167
+ form?.getAttribute("name"),
168
+ form?.getAttribute("aria-label")
169
+ ];
170
+ const boundUrl = (url) => {
171
+ const hash = url.indexOf("#");
172
+ return hash === -1 || /^#[/!]/.test(url.slice(hash)) ? url : url.slice(0, hash);
173
+ };
174
+ const inspectInput = (element, plan) => {
175
+ const metadata = details(element, element, 0, 0);
176
+ const control = activationTarget(element) ?? element;
177
+ const form = isInput(control) || isButton(control) || isTextArea(control) || isSelect(control) ? control.form : null;
178
+ const key = plan.keys === null ? void 0 : /(?:^|\+)(\+|[^+]+)$/.exec(plan.keys)?.[1];
179
+ const enter = key === "Enter";
180
+ const activation = plan.action === "click" || plan.action === "press" && (enter || key === "Space");
181
+ const fieldEnter = isInput(control) && ![
182
+ "button",
183
+ "reset",
184
+ "submit",
185
+ "image",
186
+ "file",
187
+ "color",
188
+ "hidden"
189
+ ].includes(control.type) && (plan.action === "press" && enter || plan.action === "type" && plan.submit);
190
+ const submits = form !== null && (activation && isSubmitter(control) || fieldEnter);
191
+ const submitter = isSubmitter(control) ? control : form === null ? void 0 : defaultSubmitter(form);
192
+ const formDestination = form === null ? void 0 : submitter?.hasAttribute("formaction") === true ? submitter.formAction : form.action;
193
+ const formMethod = form === null ? void 0 : submitter?.hasAttribute("formmethod") === true ? submitter.formMethod : form.method;
194
+ const formTarget = form === null ? void 0 : submitter?.hasAttribute("formtarget") === true ? submitter.formTarget : form.target;
195
+ const link = linkOf(element);
196
+ const href = link === void 0 ? null : hrefAttribute(link);
197
+ const sends = submits && formMethod !== "dialog";
198
+ const destination = sends ? formDestination : activation ? metadata.href : void 0;
199
+ const acts = plan.action !== "hover" && plan.action !== "scroll";
200
+ const builtIn = sends || href !== null && !/^\s*(?:#\s*|javascript:[^]*)$/i.test(href) || isInput(control) && ![
201
+ "button",
202
+ "submit",
203
+ "image",
204
+ "reset"
205
+ ].includes(control.type) || (isInput(control) || isButton(control)) && control.type === "reset" && form !== null || control.tagName === "SUMMARY" || control.closest("select") !== null || textEntry(control);
206
+ const facts = [];
207
+ if (sends) facts.push("form-submit");
208
+ if (activation && link?.hasAttribute("download") === true) facts.push("download");
209
+ if (activation && isInput(control) && control.type === "file") facts.push("upload");
210
+ if (plan.action === "type" && isSecret(control) || sends && form !== null && Array.from(form.elements).some((field) => isField(field) && isSecret(field) && isFilled(field))) facts.push("secret");
211
+ if (activation && !builtIn) facts.push("scripted");
212
+ if (acts && (metadata.role === "canvas" || metadata.role === "iframe" || metadata.name === "")) facts.push("opaque");
213
+ const role = metadata.role;
214
+ const boundName = role !== null && (interactiveRoles.has(role) || role === "canvas" || role === "iframe") && !(isHtml(element) && element.isContentEditable) ? metadata.name : null;
215
+ const fingerprint = JSON.stringify([
216
+ purpose(control, form),
217
+ element.tagName,
218
+ element.id,
219
+ role,
220
+ boundName,
221
+ metadata.href,
222
+ control === element ? null : refFor(control),
223
+ isInput(control) || isButton(control) ? control.type : control.tagName,
224
+ control.matches(":disabled") || isDisabled(control),
225
+ control.getAttribute("readonly"),
226
+ control.getAttribute("aria-readonly"),
227
+ isHtml(control) && control.isContentEditable,
228
+ control.getAttribute("name"),
229
+ control.getAttribute("accept"),
230
+ control.hasAttribute("multiple"),
231
+ link?.getAttribute("download"),
232
+ form === null ? null : refFor(form),
233
+ form === null ? null : boundUrl(form.action),
234
+ form?.method,
235
+ form?.target,
236
+ submitter === void 0 ? null : refFor(submitter),
237
+ formDestination === void 0 ? void 0 : boundUrl(formDestination),
238
+ formMethod,
239
+ formTarget,
240
+ submitter?.formNoValidate,
241
+ form?.noValidate
242
+ ]);
243
+ return {
244
+ inspected: {
245
+ ref: refFor(element),
246
+ element: metadata.element,
247
+ role: metadata.role,
248
+ name: metadata.name,
249
+ cursor: metadata.cursor,
250
+ ...metadata.href === void 0 ? {} : { href: metadata.href },
251
+ secret: isSecret(control),
252
+ fingerprint
253
+ },
254
+ facts,
255
+ destination,
256
+ explain: () => evidenceOf(element, form, formMethod, formDestination)
257
+ };
258
+ };
259
+ const inspect = (plan) => {
260
+ const targets = [];
261
+ const facts = /* @__PURE__ */ new Set();
262
+ let destination = plan.destination ?? void 0;
263
+ let explain = () => ({ title: clean(document.title, 120) });
264
+ for (const [index, target] of plan.targets.entries()) {
265
+ let element;
266
+ if (target === null) element = activeElement();
267
+ else if (typeof target === "string") {
268
+ element = lookup(target);
269
+ if (element === void 0) return {
270
+ error: "stale",
271
+ detail: target + " is not on the page any more",
272
+ index
273
+ };
274
+ } else {
275
+ if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || target.x < 0 || target.y < 0 || target.x >= window.innerWidth || target.y >= window.innerHeight) return {
276
+ error: "outside",
277
+ detail: "the point is outside the viewport"
278
+ };
279
+ const hit = hitAt(document, target.x, target.y);
280
+ element = hit === null ? null : controlOf(hit);
281
+ if (element === null) return {
282
+ error: "offscreen",
283
+ detail: "nothing is painted at the point"
284
+ };
285
+ }
286
+ if (element === null) {
287
+ targets.push(null);
288
+ continue;
289
+ }
290
+ if (!inCurrentDocument(element)) return {
291
+ error: "stale",
292
+ detail: "the input target belongs to a replaced document",
293
+ index
294
+ };
295
+ const refusal = plan.action === "type" ? typingRefusal(element, target !== null) : void 0;
296
+ if (refusal !== void 0) return {
297
+ error: "untypeable",
298
+ detail: refusal
299
+ };
300
+ const inspected = inspectInput(element, plan);
301
+ if (index === 0) explain = inspected.explain;
302
+ targets.push(inspected.inspected);
303
+ for (const fact of inspected.facts) facts.add(fact);
304
+ destination ??= inspected.destination;
305
+ if (inspected.destination !== void 0) try {
306
+ if (new URL(inspected.destination, location.href).origin !== location.origin) facts.add("cross-origin");
307
+ } catch {
308
+ return {
309
+ error: "outside",
310
+ detail: "the input destination is not a valid URL"
311
+ };
312
+ }
313
+ }
314
+ if (destination !== void 0) try {
315
+ destination = new URL(destination, location.href).href;
316
+ if (new URL(destination).origin !== location.origin) facts.add("cross-origin");
317
+ } catch {
318
+ return {
319
+ error: "outside",
320
+ detail: "the input destination is not a valid URL"
321
+ };
322
+ }
323
+ return {
324
+ url: boundUrl(location.href),
325
+ targets,
326
+ facts: [...facts],
327
+ ...destination === void 0 ? {} : { destination },
328
+ explain
329
+ };
330
+ };
331
+ const prepareInput = (plan) => {
332
+ const inspected = inspect(plan);
333
+ if ("error" in inspected) return inspected;
334
+ const { explain, ...prepared } = inspected;
335
+ return {
336
+ ...prepared,
337
+ evidence: explain()
338
+ };
339
+ };
340
+ const validate = (plan, prepared, options) => {
341
+ const current = inspect(plan);
342
+ if ("error" in current) return current.error === "stale" ? current : {
343
+ error: "changed",
344
+ detail: current.detail
345
+ };
346
+ if (current.url !== prepared.url || (current.destination === void 0 || prepared.destination === void 0 ? current.destination !== prepared.destination : boundUrl(current.destination) !== boundUrl(prepared.destination)) || JSON.stringify(current.facts) !== JSON.stringify(prepared.facts) || current.targets.length !== prepared.targets.length || current.targets.some((target, index) => {
347
+ const previous = prepared.targets[index];
348
+ return target === null ? previous !== null : previous === null || previous === void 0 || target.ref !== previous.ref || target.fingerprint !== previous.fingerprint;
349
+ })) return {
350
+ error: "changed",
351
+ detail: "the page or input target changed while the policy was deciding"
352
+ };
353
+ if (options.focused === true) {
354
+ const active = activeElement();
355
+ const expected = prepared.targets[0];
356
+ if (active === null || expected === null || expected === void 0 || refFor(active) !== expected.ref) return {
357
+ error: "changed",
358
+ detail: "focus moved away from the approved element"
359
+ };
360
+ }
361
+ for (const press of options.presses ?? []) {
362
+ const expected = prepared.targets[press.index];
363
+ const element = expected === null || expected === void 0 ? void 0 : lookup(expected.ref);
364
+ const hit = hitAt(document, press.x, press.y);
365
+ if (element === void 0 || hit === null || !receives(element, hit)) return {
366
+ error: "changed",
367
+ detail: `the approved target is no longer under the pointer at (${press.x}, ${press.y})`
368
+ };
369
+ const nested = nestedControl(element, hit);
370
+ if (nested !== void 0) return {
371
+ error: "changed",
372
+ detail: `${describe(nested)} inside the approved target would receive the press at (${press.x}, ${press.y})`
373
+ };
374
+ }
375
+ return { ok: true };
376
+ };
377
+ const validateInput = (plan, prepared, options = {}) => {
378
+ if ((options.presses ?? []).length === 0) return validate(plan, prepared, options);
379
+ const { promise, resolve } = Promise.withResolvers();
380
+ if (document.visibilityState === "hidden") resolve();
381
+ else requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
382
+ return promise.then(() => validate(plan, prepared, options));
383
+ };
384
+ return {
385
+ prepareInput,
386
+ textEntry,
387
+ typingRefusal,
388
+ validateInput
389
+ };
390
+ };
391
+ const InputPreparationError = Schema.Struct({
392
+ error: Schema.String,
393
+ detail: Schema.String,
394
+ index: Schema.optional(Schema.Finite)
395
+ });
396
+ const PreparedInputResultSchema = Schema.Union([Schema.Struct({
397
+ url: Schema.String,
398
+ targets: Schema.Array(Schema.NullOr(Schema.Struct({
399
+ ref: Schema.String,
400
+ element: Schema.String,
401
+ role: Schema.NullOr(Schema.String),
402
+ name: Schema.String,
403
+ cursor: Schema.String,
404
+ href: Schema.optional(Schema.String),
405
+ secret: Schema.Boolean,
406
+ fingerprint: Schema.String
407
+ }))),
408
+ facts: Schema.Array(Fact$1),
409
+ destination: Schema.optional(Schema.String),
410
+ evidence: EvidenceSchema
411
+ }), InputPreparationError]);
412
+ const ValidatedInputResultSchema = Schema.Union([Schema.Struct({ ok: Schema.Literal(true) }), InputPreparationError]);
413
+ //#endregion
414
+ //#region src/Page.ts
415
+ /**
416
+ * One browser tab: navigation, snapshots, pictures and input.
417
+ *
418
+ * A page waits only for itself: its operations take turns in one lane of its own. An action, which
419
+ * sends input or navigates, has the page to itself, in the order actions were asked; reads share
420
+ * it, after the action in flight and every action asked before them, so a read describes the page
421
+ * an action left. A wait for a turn ends at the operation's deadline as `Busy`, never `Timeout`,
422
+ * and a full deadline then bounds the operation itself; `failFast` fails it at once instead.
423
+ * Identical reads asked between the same actions share one call to the page, and one whose
424
+ * callers gave up finishes for the next caller to ask it. A policy holds outside the lane while
425
+ * other operations continue; its target is revalidated before dispatch. Element targets are refs
426
+ * from a snapshot; point targets are viewport coordinates in CSS pixels, the same coordinates as a
427
+ * screenshot's pixels. Mouse and keyboard input share a bounded pipeline, so pacing does not wait
428
+ * for each protocol reply. Target lookup happens before the input is sent.
429
+ *
430
+ * @since 0.3.0
431
+ */
432
+ var Page_exports = /* @__PURE__ */ __exportAll({
433
+ ElementState: () => ElementState,
434
+ Fact: () => Fact,
435
+ Found: () => Found,
436
+ InputRequest: () => InputRequest,
437
+ Region: () => Region,
438
+ ResolvedTarget: () => ResolvedTarget,
439
+ State: () => State,
440
+ Text: () => Text,
441
+ Zoom: () => Zoom,
442
+ correlate: () => correlate,
443
+ failFast: () => failFast,
444
+ redacted: () => redacted
445
+ });
446
+ /** An integer rectangle in viewport CSS pixels. */
447
+ var Region = class extends Schema.Class("effect-browser/Region")({
448
+ x: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
449
+ y: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
450
+ width: Schema.Int.check(Schema.isGreaterThan(0)),
451
+ height: Schema.Int.check(Schema.isGreaterThan(0))
452
+ }) {};
453
+ /** A crop and its viewport origin: add that origin to a point read from the crop. */
454
+ var Zoom = class extends Schema.Class("effect-browser/Zoom")({
455
+ page: Schema.String,
456
+ region: Region,
457
+ image: Image
458
+ }) {};
459
+ /** The target as it read before input. Point coordinates are never snapped to another position. */
460
+ var ResolvedTarget = class extends Schema.Class("effect-browser/ResolvedTarget")({
461
+ point: Schema.Struct({
462
+ x: Schema.Finite,
463
+ y: Schema.Finite
464
+ }),
465
+ element: Schema.String,
466
+ /** The element's lowercase tag name. */
467
+ tag: Schema.String,
468
+ role: Schema.NullOr(Schema.String),
469
+ name: Schema.String,
470
+ context: SubjectContext,
471
+ /** For a point target, the box of what it found there. */
472
+ box: Schema.optional(Box),
473
+ cursor: Schema.String,
474
+ href: Schema.optional(Schema.String)
475
+ }) {};
476
+ /**
477
+ * An element's state as its markup gives it: `checked` for what can be checked, `expanded`,
478
+ * `selected` and `pressed` where the page says either way, and `level` for headings.
479
+ */
480
+ const ElementState = Schema.Struct({
481
+ disabled: Schema.Boolean,
482
+ focused: Schema.Boolean,
483
+ checked: Schema.optional(Schema.Boolean),
484
+ expanded: Schema.optional(Schema.Boolean),
485
+ selected: Schema.optional(Schema.Boolean),
486
+ pressed: Schema.optional(Schema.Boolean),
487
+ level: Schema.optional(Schema.Int)
488
+ });
489
+ /** An element `find` found: a ref for the actions, what it is, where it is and its state. */
490
+ var Found = class extends Schema.Class("effect-browser/Found")({
491
+ ref: Schema.String,
492
+ subject: Subject,
493
+ /** Its box, rounded. Reading the document, it may be out of view. */
494
+ box: Box,
495
+ inViewport: Schema.Boolean,
496
+ state: ElementState
497
+ }) {};
498
+ /** What a page showed as text: a line per block, with table cells apart by tabs. */
499
+ var Text = class extends Schema.Class("effect-browser/Text")({
500
+ url: Schema.String,
501
+ title: Schema.String,
502
+ text: Schema.String,
503
+ /** True when `text` was cut at `maxChars`. */
504
+ truncated: Schema.Boolean,
505
+ /** Host monotonic milliseconds from the browser's captured Effect Clock. */
506
+ at: Schema.Finite
507
+ }) {};
508
+ /**
509
+ * What the library knows of a page now, without asking it, each part with when it learned it, in
510
+ * host monotonic milliseconds: a part's age is `at` less its time. Text read before the current
511
+ * document began is left out, since it tells of a document that has gone.
512
+ */
513
+ var State = class extends Schema.Class("effect-browser/PageState")({
514
+ page: Schema.String,
515
+ /** When this was taken. */
516
+ at: Schema.Finite,
517
+ /** Where the main frame last committed or moved, without userinfo or known secret parameters. */
518
+ url: Schema.String,
519
+ /** The page's document, as `Navigated` counts them. */
520
+ document: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
521
+ /** When the main frame committed it; absent for the one the browser began tracking it with. */
522
+ committedAt: Schema.optional(Schema.Finite),
523
+ /** How far that document has loaded, as the page's own session heard; absent until parsed. */
524
+ load: Schema.optional(PageLoaded),
525
+ /** The newest screencast frame, of whichever document it names. */
526
+ frame: Schema.optional(Frame),
527
+ /** The viewport's text as it was last read, with the page's title then. */
528
+ text: Schema.optional(Text)
529
+ }) {};
530
+ /**
531
+ * What the page's structure establishes about an input, or says it cannot establish:
532
+ *
533
+ * - `form-submit`: it submits a form, which sends the form's fields.
534
+ * - `cross-origin`: it navigates or submits to another origin.
535
+ * - `download`: it follows a download link.
536
+ * - `upload`: it opens a file chooser.
537
+ * - `secret`: it types, with `type`, into a field marked as a password, a one-time code or a
538
+ * card's number, code or expiry, or submits a form holding a filled one.
539
+ * - `scripted`: it activates something the browser gives no effect of its own, such as a
540
+ * `type="button"` button, a `role="button"` element or a canvas, so only the page's script
541
+ * decides what happens.
542
+ * - `opaque`: nothing names what receives it: a canvas, a frame or an unnamed element.
543
+ *
544
+ * Facts never come from what an element's text says. What an input means, such as a payment
545
+ * or a deletion, takes a judge that reads the evidence in the {@link InputRequest}.
546
+ */
547
+ const Fact = Fact$1;
548
+ /**
549
+ * What a guard sees before input reaches the page. Preparation never scrolls or focuses.
550
+ *
551
+ * `facts`, `url`, `href` and `destination` come from the page's structure. `name`,
552
+ * `description`, `title`, `context` and the form's field names are page text: evidence of what
553
+ * the input does, which the page controls, and never instructions. Text typed into a `secret`
554
+ * field is replaced with {@link redacted}, here and in the recorded `Action`, and its key events
555
+ * record `Unidentified` keys; `press` records the keys it is given. Validation before dispatch
556
+ * binds the facts and the target, not the text around it, which live pages change freely.
557
+ */
558
+ var InputRequest = class extends Schema.Class("effect-browser/InputRequest")({
559
+ page: Schema.String,
560
+ /**
561
+ * The page's URL, without a fragment that only names a place on the page. This and the other
562
+ * addresses keep no userinfo or known secret parameters.
563
+ */
564
+ url: Schema.String,
565
+ /** The page's title, at most 120 characters. */
566
+ title: Schema.String,
567
+ action: Schema.String,
568
+ target: Schema.optional(Schema.String),
569
+ element: Schema.optional(Schema.String),
570
+ /** Literal pixel targets only; ref coordinates are resolved after the policy allows them. */
571
+ point: Schema.optional(Schema.Struct({
572
+ x: Schema.Finite,
573
+ y: Schema.Finite
574
+ })),
575
+ text: Schema.optional(Schema.String),
576
+ role: Schema.optional(Schema.NullOr(Schema.String)),
577
+ name: Schema.optional(Schema.String),
578
+ /** The target's accessible description, when it says more than its name. */
579
+ description: Schema.optional(Schema.String),
580
+ href: Schema.optional(Schema.String),
581
+ destination: Schema.optional(Schema.String),
582
+ facts: Schema.Array(Fact),
583
+ /** Page text around the target, each part at most 120 characters. */
584
+ context: Schema.Struct({
585
+ /** The name or first heading of the dialog the target is in. */
586
+ dialog: Schema.optional(Schema.String),
587
+ /** The nearest heading before the target. */
588
+ heading: Schema.optional(Schema.String),
589
+ /** The visible text just before the target in its row, item, group or form, or after it. */
590
+ nearby: Schema.optional(Schema.String)
591
+ }),
592
+ /** The form the target is in, with at most 16 of its fields. */
593
+ form: Schema.optional(Schema.Struct({
594
+ method: Schema.String,
595
+ action: Schema.String,
596
+ /** Each field's type, name and autocomplete tokens, and whether it is filled: never its value. */
597
+ fields: Schema.Array(FormFieldSchema)
598
+ }))
599
+ }) {};
600
+ /**
601
+ * What replaces text typed into a `secret` field, in requests and in the recorded `Action`, and a
602
+ * credential's value in every address the library reports.
603
+ */
604
+ const redacted = withheld;
605
+ /**
606
+ * Run page operations without waiting their turn: on a page busy with other operations, each fails
607
+ * `Busy` at once, and `Browser.newPage` on a browser with `maxPages` open fails `Limit` at once.
608
+ */
609
+ const failFast = (effect) => Effect.provideService(effect, FailFast, true);
610
+ /**
611
+ * Give every action `effect` performs a caller's id, such as a model's tool call id, which its
612
+ * recorded `Action` carries as `correlation`.
613
+ */
614
+ const correlate = (id) => (effect) => Effect.provideService(effect, Correlation, id);
615
+ //#endregion
616
+ export { evidence as _, Page_exports as a, State as c, correlate as d, failFast as f, guard as g, ValidatedInputResultSchema as h, InputRequest as i, Text as l, PreparedInputResultSchema as m, Fact as n, Region as o, redacted as p, Found as r, ResolvedTarget as s, ElementState as t, Zoom as u };
617
+
618
+ //# sourceMappingURL=Page-B5cjaUp5.mjs.map