browser-debugger-cli 0.8.0 → 0.9.0

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 (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -0,0 +1,123 @@
1
+ /**
2
+ * CDP events a DOM action causes, for its "what changed" report: main-frame
3
+ * navigations (with document statuses), requests and new windows.
4
+ */
5
+ import { createLogger } from '../../ui/logging/index.js';
6
+ import { getErrorMessage } from '../../utils/errors.js';
7
+ const log = createLogger('dom');
8
+ /**
9
+ * Listen for the main frame's navigations, document responses, requests
10
+ * and new windows. Events of attached child targets (out-of-process frames,
11
+ * workers) are not main-frame navigations, but their requests count.
12
+ * `Network.enable` is sent without waiting (see `withActionStability`): the
13
+ * session's network collector usually has the domain enabled already.
14
+ *
15
+ * @param cdp - CDP connection
16
+ * @returns Live listener
17
+ */
18
+ export function listenForActivity(cdp) {
19
+ const state = {
20
+ events: { statusByLoader: new Map() },
21
+ mainFrame: new Set(),
22
+ requests: 0,
23
+ opened: false,
24
+ loading: false,
25
+ };
26
+ const cleanups = [
27
+ ...listenForNavigation(cdp, state),
28
+ ...listenForRequests(cdp, state),
29
+ ...listenForWindows(cdp, state),
30
+ ];
31
+ findMainFrame(cdp, state);
32
+ return {
33
+ events: state.events,
34
+ activity: () => ({ requests: state.requests, opened: state.opened }),
35
+ navigationPending: () => state.loading,
36
+ dispose: () => cleanups.forEach((cleanup) => cleanup()),
37
+ };
38
+ }
39
+ /**
40
+ * Main-frame navigations: new documents, same-document URL changes, and
41
+ * whether a load is pending (started, not committed or stopped yet).
42
+ *
43
+ * @param cdp - CDP connection
44
+ * @param state - State to record into
45
+ * @returns Listener cleanups
46
+ */
47
+ function listenForNavigation(cdp, state) {
48
+ const isMain = (frameId, sessionId) => sessionId === undefined && state.mainFrame.has(frameId);
49
+ return [
50
+ cdp.on('Page.frameNavigated', ({ frame }, sessionId) => {
51
+ if (sessionId !== undefined || frame.parentId !== undefined)
52
+ return;
53
+ state.mainFrame.add(frame.id);
54
+ state.loading = false;
55
+ const url = frame.url + (frame.urlFragment ?? '');
56
+ state.events.document = { url, loaderId: frame.loaderId };
57
+ }),
58
+ cdp.on('Page.navigatedWithinDocument', (params, session) => {
59
+ if (isMain(params.frameId, session))
60
+ state.events.withinDocumentUrl = params.url;
61
+ }),
62
+ cdp.on('Page.frameStartedLoading', ({ frameId }, sessionId) => {
63
+ if (isMain(frameId, sessionId))
64
+ state.loading = true;
65
+ }),
66
+ cdp.on('Page.frameStoppedLoading', ({ frameId }, sessionId) => {
67
+ if (isMain(frameId, sessionId))
68
+ state.loading = false;
69
+ }),
70
+ ];
71
+ }
72
+ /**
73
+ * Requests (any target) and the statuses of documents.
74
+ *
75
+ * @param cdp - CDP connection
76
+ * @param state - State to record into
77
+ * @returns Listener cleanups
78
+ */
79
+ function listenForRequests(cdp, state) {
80
+ void cdp
81
+ .send('Network.enable')
82
+ .catch((error) => log.debug(`Network.enable failed: ${getErrorMessage(error)}`));
83
+ return [
84
+ cdp.on('Network.responseReceived', ({ type, loaderId, response }) => {
85
+ if (type === 'Document' && loaderId) {
86
+ state.events.statusByLoader.set(loaderId, response.status);
87
+ }
88
+ }),
89
+ cdp.on('Network.requestWillBeSent', () => {
90
+ state.requests++;
91
+ }),
92
+ ];
93
+ }
94
+ /**
95
+ * New windows, tabs and downloads.
96
+ *
97
+ * @param cdp - CDP connection
98
+ * @param state - State to record into
99
+ * @returns Listener cleanups
100
+ */
101
+ function listenForWindows(cdp, state) {
102
+ const opened = () => {
103
+ state.opened = true;
104
+ };
105
+ return [cdp.on('Page.windowOpen', opened), cdp.on('Page.downloadWillBegin', opened)];
106
+ }
107
+ /**
108
+ * Learn the main frame's id (until a navigation reports it), without waiting.
109
+ *
110
+ * @param cdp - CDP connection
111
+ * @param state - State to record into
112
+ */
113
+ function findMainFrame(cdp, state) {
114
+ void cdp
115
+ .send('Page.getFrameTree')
116
+ .then((tree) => {
117
+ const id = tree.frameTree?.frame?.id;
118
+ if (id)
119
+ state.mainFrame.add(id);
120
+ })
121
+ .catch((error) => log.debug(`No frame tree: ${getErrorMessage(error)}`));
122
+ }
123
+ //# sourceMappingURL=pageActivity.js.map
@@ -6,6 +6,34 @@
6
6
  * to properly trigger React's event system.
7
7
  */
8
8
  import type { FillResult, ClickResult } from '../../ipc/protocol/domTypes.js';
9
+ /**
10
+ * Page-side read-back of a filled field: a mismatch when its value is not
11
+ * what was asked for (the page rejected, reformatted or moved the input),
12
+ * undefined when it is. Values are compared as the browser normalises them:
13
+ * colors case-insensitively, numbers and ranges as numbers, email trimmed,
14
+ * textarea line endings as `\n`, times and local date-times without zero
15
+ * seconds and with `T` (`2024-01-05 10:00:00` is `2024-01-05T10:00`).
16
+ * Checkboxes and radios compare as `checked`/`unchecked`, a multiple select
17
+ * as its selected values joined by ", ", contenteditable text with
18
+ * whitespace collapsed. A value cut to the field's maxlength sets
19
+ * `truncatedTo`; a password mismatch gives masked values and both lengths.
20
+ */
21
+ export declare const FILL_VALUE_MISMATCH_JS = "(field, expected) => {\n const type = (field.type || '').toLowerCase();\n const actual = field.isContentEditable\n ? (field.textContent || '')\n : type === 'checkbox' || type === 'radio'\n ? (field.checked ? 'checked' : 'unchecked')\n : field.localName === 'select' && field.multiple\n ? Array.from(field.selectedOptions).map((o) => o.value).join(', ')\n : String(field.value);\n const time = (text) => text.trim().replace(' ', 'T').replace(/(\\d\\d:\\d\\d):00(\\.0+)?$/, '$1');\n const normalize = (text) => {\n text = String(text);\n if (field.isContentEditable) return text.replace(/\\s+/g, ' ').trim();\n if (field.localName === 'textarea') return text.replace(/\\r\\n?/g, '\\n');\n if (type === 'color') return text.trim().toLowerCase();\n if (type === 'email') return text.trim();\n if (type === 'time' || type === 'datetime-local') return time(text);\n return text;\n };\n const numeric = (type === 'number' || type === 'range') && actual.trim() !== '' && String(expected).trim() !== '';\n const same = numeric ? Number(actual) === Number(expected) : normalize(actual) === normalize(expected);\n if (same) return undefined;\n if (type === 'password') {\n const mask = (text) => (text === '' ? '' : '********');\n return { expected: mask(expected), actual: mask(actual), expectedLength: expected.length, actualLength: actual.length };\n }\n const cut = field.maxLength > 0 && actual.length === field.maxLength && expected.length > actual.length &&\n expected.startsWith(actual);\n return cut ? { expected: expected, actual: actual, truncatedTo: actual.length } : { expected: expected, actual: actual };\n}";
22
+ /**
23
+ * Page-side list of the other text-like fields of a field's form (of the
24
+ * document when it has none), with their values: taken before a fill, so
25
+ * {@link MOVED_VALUE_JS} can tell which one the page changed.
26
+ */
27
+ export declare const FIELD_VALUES_JS = "(field) => {\n const fields = field.form ? Array.from(field.form.elements) : Array.from(field.ownerDocument.querySelectorAll('input, textarea'));\n return new Map(fields\n .filter((f) => f !== field && /^(input|textarea)$/.test(f.localName) &&\n !/^(checkbox|radio|password|hidden|submit|button|reset|file|image)$/i.test(f.type || ''))\n .map((f) => [f, String(f.value)]));\n}";
28
+ /**
29
+ * Page-side reason `dom fill` cannot fill an element, or null when it can:
30
+ * a disabled or read-only form control (naming the attribute or the disabled
31
+ * `<fieldset>`), or for any other element the switch that keeps it from being
32
+ * editable: `contenteditable="false"` (on it or the editor around it), an
33
+ * `inert` ancestor, `aria-readonly` or `aria-disabled`; otherwise it is not a
34
+ * fillable kind of element. Messages are {@link FILL_REFUSALS}.
35
+ */
36
+ export declare const FILL_REFUSAL_JS: string;
9
37
  /**
10
38
  * JavaScript function to fill an input element in a React-compatible way.
11
39
  *
@@ -14,10 +42,36 @@ import type { FillResult, ClickResult } from '../../ipc/protocol/domTypes.js';
14
42
  * 2. Dispatches input/change events that React listens for
15
43
  * 3. Properly handles focus/blur for form validation
16
44
  *
45
+ * A `<label>` is filled through its control ({@link LABEL_CONTROL_JS}),
46
+ * reported as e.g. `input (via label)`.
47
+ *
48
+ * The result is returned right away (a change handler may navigate). The
49
+ * field and the value to expect are left in `window.__bdgFillCheck` for
50
+ * {@link FILL_READ_BACK_SCRIPT}, which reads the value back a moment later.
51
+ *
17
52
  * @remarks
18
53
  * Works with React, Vue, Angular, and vanilla JS applications.
19
54
  */
20
- export declare const REACT_FILL_SCRIPT = "\n(function(selector, parts, value, options) {\n const allMatches = (function (selector, parts) {\n if (selector === '__bdg_bound_target__') {\n const el = window.__bdgTarget;\n return el && el.isConnected ? [el] : [];\n }\n return (function (selector, parts) {\n const css = parts ? parts.map((part) => part.css).join(', ') : selector;\n const found = [];\n const visit = (root) => {\n for (const match of root.querySelectorAll(css)) found.push(match);\n for (const el of root.querySelectorAll('*')) {\n if (el.shadowRoot) visit(el.shadowRoot);\n if (el.tagName === 'IFRAME' || el.tagName === 'FRAME') {\n let frameDocument = null;\n try { frameDocument = el.contentDocument; } catch (e) { frameDocument = null; }\n if (frameDocument) visit(frameDocument);\n }\n }\n };\n visit(document);\n if (!parts) return found;\n const passes = (el, filter) => {\n if (filter.kind === 'visible') {\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';\n }\n const text = (typeof el.innerText === 'string' ? el.innerText : el.textContent || '').replace(/\\s+/g, ' ').trim();\n return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);\n};\n return found.filter((el) => parts.some((part) => el.matches(part.css) && part.filters.every((filter) => passes(el, filter))));\n})(selector, parts);\n})(selector, parts);\n const warnings = [];\n // Why a user could not reach the field (the value is still set, so scripted\n // flows keep working, but the result may not be what a user would see)\n const unreachableReason = (field) => {\n if (field.closest('[inert]')) return 'inert';\n const modal = field.ownerDocument.querySelector('dialog:modal');\n if (modal && !modal.contains(field)) return 'behind an open modal dialog';\n if (field.checkVisibility && !field.checkVisibility({ visibilityProperty: true, opacityProperty: true })) return 'hidden';\n return null;\n };\n // Why the browser did not take the value as given (it sanitizes instead of\n // throwing: bad dates become \"\", colors #000000, ranges are clamped)\n const rejectedValue = (field, type, text) => {\n const formats = {\n number: 'a number',\n date: 'YYYY-MM-DD',\n time: 'HH:MM',\n 'datetime-local': 'YYYY-MM-DDTHH:MM',\n month: 'YYYY-MM',\n week: 'YYYY-Www'\n };\n const rejected = 'The browser rejected \"' + text + '\" for a ' + type + ' field (it keeps its previous value)';\n if (formats[type] && text.trim() !== '' && field.value === '') {\n return { error: rejected, suggestion: 'Expected ' + formats[type] };\n }\n if (type === 'color' && field.value.toLowerCase() !== text.trim().toLowerCase()) {\n return { error: rejected, suggestion: 'Expected a hex color like #1a2b3c' };\n }\n if (type === 'range' && Number(field.value) !== Number(text)) {\n return {\n error: 'The browser would set ' + field.value + ' instead of \"' + text + '\" (range ' + (field.min || 0) + ' to ' + (field.max || 100) + ', step ' + (field.step || 1) + ')',\n suggestion: 'Use a value within the range that matches the step'\n };\n }\n return null;\n };\n \n if (allMatches.length === 0) {\n return { \n success: false, \n error: 'Element not found: ' + selector,\n selector: selector\n };\n }\n \n let el;\n const index = options.index;\n \n // If index is provided, use it directly (0-based)\n if (typeof index === 'number' && index >= 0) {\n if (index >= allMatches.length) {\n return {\n success: false,\n error: 'Index out of range',\n selector: selector,\n matchCount: allMatches.length,\n requestedIndex: index,\n suggestion: 'Use --index between 0 and ' + (allMatches.length - 1)\n };\n }\n el = allMatches[index];\n } else {\n el = allMatches[0];\n }\n \n const tagName = el.tagName.toLowerCase();\n const inputType = el.type?.toLowerCase();\n \n const isFillable = (\n tagName === 'input' || \n tagName === 'textarea' || \n tagName === 'select' ||\n el.isContentEditable\n );\n \n if (!isFillable) {\n return {\n success: false,\n error: 'Element is not fillable',\n elementType: tagName,\n suggestion: 'Only input, textarea, select, and contenteditable elements can be filled'\n };\n }\n\n if (el.disabled || el.matches(':disabled')) {\n return {\n success: false,\n error: 'Element is disabled',\n elementType: tagName,\n suggestion: 'Enable the field first (it may depend on another input)'\n };\n }\n if (el.readOnly) {\n return {\n success: false,\n error: 'Element is read-only',\n elementType: tagName,\n suggestion: 'Read-only fields cannot be filled'\n };\n }\n\n const unreachable = unreachableReason(el);\n if (unreachable) {\n warnings.push('The field is ' + unreachable + '; a user could not fill it (the value was set anyway)');\n }\n\n el.focus();\n\n if (tagName === 'select' && el.multiple) {\n // Several options, separated by commas (an option whose value contains a\n // comma matches as a whole); \"\" selects none\n const options = Array.from(el.options);\n const whole = options.some((o) => o.value === value || o.text.trim() === value);\n const wanted = whole ? [value] : value.split(',').map((part) => part.trim()).filter(Boolean);\n const chosen = wanted.map((part) =>\n options.find((o) => o.value === part) || options.find((o) => o.text.trim() === part)\n );\n const missing = wanted.find((part, i) => !chosen[i]);\n if (missing !== undefined) {\n return {\n success: false,\n error: 'Option not found: ' + missing,\n exitCode: 81,\n elementType: tagName,\n suggestion: 'Available options: ' + options.slice(0, 10).map((o) => o.value || o.text.trim()).join(', ')\n };\n }\n options.forEach((o) => { o.selected = chosen.includes(o); });\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n } else if (tagName === 'select') {\n // Match by option value first, then by visible label\n const options = Array.from(el.options);\n const option =\n options.find((o) => o.value === value) ||\n options.find((o) => o.text.trim() === value);\n if (!option) {\n return {\n success: false,\n error: 'Option not found: ' + value,\n exitCode: 81,\n elementType: tagName,\n suggestion: 'Available options: ' + options.slice(0, 10).map((o) => o.value || o.text.trim()).join(', ')\n };\n }\n el.value = option.value;\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n } else if (inputType === 'checkbox' || inputType === 'radio') {\n // Toggle through a click, like a user: frameworks (React) track checkable\n // state via click events and revert a programmatic `checked` assignment\n const normalized = String(value).trim().toLowerCase();\n const truthy = ['true', '1', 'yes', 'on', 'checked'];\n const falsy = ['false', '0', 'no', 'off', 'unchecked'];\n if (!truthy.includes(normalized) && !falsy.includes(normalized)) {\n return {\n success: false,\n error: 'Expected true or false for a ' + inputType + ', got \"' + value + '\"',\n elementType: tagName,\n inputType: inputType,\n suggestion: 'Use true/false (also yes/no, on/off, 1/0)'\n };\n }\n const shouldCheck = truthy.includes(normalized);\n if (inputType === 'radio' && !shouldCheck) {\n return {\n success: false,\n error: 'A radio button cannot be unchecked',\n elementType: tagName,\n inputType: inputType,\n suggestion: 'Select another option in the same group instead'\n };\n }\n if (el.checked !== shouldCheck) {\n el.click();\n }\n } else if (inputType === 'file') {\n return {\n success: false,\n fileInput: true,\n elementType: tagName,\n inputType: inputType,\n error: 'File input'\n };\n } else if (el.isContentEditable) {\n el.textContent = value;\n el.dispatchEvent(new Event('input', { bubbles: true }));\n } else {\n const nativeInputValueSetter = Object.getOwnPropertyDescriptor(\n el.ownerDocument.defaultView.HTMLInputElement.prototype,\n 'value'\n )?.set;\n \n const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(\n el.ownerDocument.defaultView.HTMLTextAreaElement.prototype,\n 'value'\n )?.set;\n \n const setter = tagName === 'textarea' \n ? nativeTextAreaValueSetter \n : nativeInputValueSetter;\n\n if (el.maxLength > 0 && value.length > el.maxLength) {\n return {\n success: false,\n error: 'Value is ' + value.length + ' characters; the field accepts at most ' + el.maxLength,\n elementType: tagName,\n inputType: inputType || null,\n suggestion: 'Shorten the value (a user could not type more than maxlength characters)'\n };\n }\n \n const setValue = (text) => (setter ? setter.call(el, text) : (el.value = text));\n const previous = el.value;\n setValue(value);\n const rejection = rejectedValue(el, inputType, value);\n if (rejection) {\n setValue(previous);\n if (options.blur !== false) el.blur();\n return {\n success: false,\n error: rejection.error,\n elementType: tagName,\n inputType: inputType,\n suggestion: rejection.suggestion\n };\n }\n\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n if (el.validity && (el.validity.rangeOverflow || el.validity.rangeUnderflow)) {\n warnings.push('The value is outside the allowed range (' + (el.min || 'no minimum') + ' to ' + (el.max || 'no maximum') + '); the form will not submit until it is fixed');\n }\n }\n \n if (options.blur !== false) {\n el.blur();\n }\n \n return {\n success: true,\n selector: selector,\n value: el.isContentEditable\n ? el.textContent\n : inputType === 'password'\n ? '********'\n : tagName === 'select' && el.multiple\n ? Array.from(el.selectedOptions).map((o) => o.value).join(', ')\n : el.value,\n elementType: tagName,\n inputType: inputType || null,\n checked: inputType === 'checkbox' || inputType === 'radio' ? el.checked : undefined,\n matchCount: allMatches.length,\n warning: warnings.concat(allMatches.length > 1 && typeof index !== 'number'\n ? [allMatches.length + ' elements match; filled the first (use --index or a more specific selector)']\n : []).join('; ') || undefined\n };\n})\n";
55
+ export declare const REACT_FILL_SCRIPT: string;
56
+ /**
57
+ * Page-side search for the field a moved value went to: one of the fields
58
+ * recorded before the fill ({@link FIELD_VALUES_JS}) whose value changed to
59
+ * the one given (trimmed, at least 2 characters). Evaluates to its id or
60
+ * name (`input#first-name`, `input[name="first"]`) or else its
61
+ * description, or undefined. Passwords are never searched for.
62
+ */
63
+ export declare const MOVED_VALUE_JS = "(field, expected, before) => {\n const wanted = String(expected).trim();\n if (!before || wanted.length < 2 || (field.type || '').toLowerCase() === 'password') return undefined;\n const other = Array.from(before.keys()).find((f) =>\n String(f.value).trim() === wanted && String(before.get(f)).trim() !== wanted);\n if (!other) return undefined;\n if (other.id) return other.localName + '#' + other.id;\n return other.name ? other.localName + '[name=\"' + other.name + '\"]' : ((node) => node.tagName.toLowerCase() +\n (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : ''))(other);\n}";
64
+ /**
65
+ * Page script reading back the field the last fill left in
66
+ * `window.__bdgFillCheck`, after one macrotask (so frameworks that render
67
+ * asynchronously have updated it). The macrotask comes from a
68
+ * `MessageChannel`, which fake timers and page code rarely replace. Evaluates
69
+ * to the mismatch ({@link FILL_VALUE_MISMATCH_JS}), with `movedTo` when the
70
+ * value turned up in another field ({@link MOVED_VALUE_JS}), or null when the
71
+ * value matches, nothing was left (the page navigated) or the field left the
72
+ * page.
73
+ */
74
+ export declare const FILL_READ_BACK_SCRIPT = "(() => {\n const check = window.__bdgFillCheck;\n delete window.__bdgFillCheck;\n if (!check) return null;\n const readBack = () => {\n if (!check.el.isConnected) return null;\n const mismatch = ((field, expected) => {\n const type = (field.type || '').toLowerCase();\n const actual = field.isContentEditable\n ? (field.textContent || '')\n : type === 'checkbox' || type === 'radio'\n ? (field.checked ? 'checked' : 'unchecked')\n : field.localName === 'select' && field.multiple\n ? Array.from(field.selectedOptions).map((o) => o.value).join(', ')\n : String(field.value);\n const time = (text) => text.trim().replace(' ', 'T').replace(/(\\d\\d:\\d\\d):00(\\.0+)?$/, '$1');\n const normalize = (text) => {\n text = String(text);\n if (field.isContentEditable) return text.replace(/\\s+/g, ' ').trim();\n if (field.localName === 'textarea') return text.replace(/\\r\\n?/g, '\\n');\n if (type === 'color') return text.trim().toLowerCase();\n if (type === 'email') return text.trim();\n if (type === 'time' || type === 'datetime-local') return time(text);\n return text;\n };\n const numeric = (type === 'number' || type === 'range') && actual.trim() !== '' && String(expected).trim() !== '';\n const same = numeric ? Number(actual) === Number(expected) : normalize(actual) === normalize(expected);\n if (same) return undefined;\n if (type === 'password') {\n const mask = (text) => (text === '' ? '' : '********');\n return { expected: mask(expected), actual: mask(actual), expectedLength: expected.length, actualLength: actual.length };\n }\n const cut = field.maxLength > 0 && actual.length === field.maxLength && expected.length > actual.length &&\n expected.startsWith(actual);\n return cut ? { expected: expected, actual: actual, truncatedTo: actual.length } : { expected: expected, actual: actual };\n})(check.el, check.expected);\n if (!mismatch) return null;\n const movedTo = ((field, expected, before) => {\n const wanted = String(expected).trim();\n if (!before || wanted.length < 2 || (field.type || '').toLowerCase() === 'password') return undefined;\n const other = Array.from(before.keys()).find((f) =>\n String(f.value).trim() === wanted && String(before.get(f)).trim() !== wanted);\n if (!other) return undefined;\n if (other.id) return other.localName + '#' + other.id;\n return other.name ? other.localName + '[name=\"' + other.name + '\"]' : ((node) => node.tagName.toLowerCase() +\n (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : ''))(other);\n})(check.el, check.expected, check.before);\n return movedTo ? Object.assign(mismatch, { movedTo: movedTo }) : mismatch;\n };\n return new Promise((resolve) => {\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n channel.port1.close();\n resolve(readBack());\n };\n channel.port2.postMessage(null);\n });\n})()";
21
75
  /**
22
76
  * JavaScript function to locate an element for clicking.
23
77
  *
@@ -30,8 +84,10 @@ export declare const REACT_FILL_SCRIPT = "\n(function(selector, parts, value, op
30
84
  * Handles both direct selector matching and indexed selection.
31
85
  * When index is provided, selects the nth matching element (0-based).
32
86
  * When selector matches multiple elements without index, prioritizes visible ones.
87
+ * A `<label>` is clicked (and double-clicked) through its control when that
88
+ * is visible ({@link LABEL_CONTROL_JS}), reported as e.g. `input (via label)`.
33
89
  */
34
- export declare const CLICK_ELEMENT_SCRIPT = "\n(function(selector, parts, index, action) {\n const allMatches = (function (selector, parts) {\n if (selector === '__bdg_bound_target__') {\n const el = window.__bdgTarget;\n return el && el.isConnected ? [el] : [];\n }\n return (function (selector, parts) {\n const css = parts ? parts.map((part) => part.css).join(', ') : selector;\n const found = [];\n const visit = (root) => {\n for (const match of root.querySelectorAll(css)) found.push(match);\n for (const el of root.querySelectorAll('*')) {\n if (el.shadowRoot) visit(el.shadowRoot);\n if (el.tagName === 'IFRAME' || el.tagName === 'FRAME') {\n let frameDocument = null;\n try { frameDocument = el.contentDocument; } catch (e) { frameDocument = null; }\n if (frameDocument) visit(frameDocument);\n }\n }\n };\n visit(document);\n if (!parts) return found;\n const passes = (el, filter) => {\n if (filter.kind === 'visible') {\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';\n }\n const text = (typeof el.innerText === 'string' ? el.innerText : el.textContent || '').replace(/\\s+/g, ' ').trim();\n return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);\n};\n return found.filter((el) => parts.some((part) => el.matches(part.css) && part.filters.every((filter) => passes(el, filter))));\n})(selector, parts);\n})(selector, parts);\n \n if (allMatches.length === 0) {\n return {\n success: false,\n error: 'Element not found: ' + selector,\n selector: selector\n };\n }\n \n let el;\n \n // If index is provided, use it directly (0-based)\n if (typeof index === 'number' && index >= 0) {\n if (index >= allMatches.length) {\n return {\n success: false,\n error: 'Index out of range',\n selector: selector,\n matchCount: allMatches.length,\n requestedIndex: index,\n suggestion: 'Use --index between 0 and ' + (allMatches.length - 1)\n };\n }\n el = allMatches[index];\n } else if (allMatches.length === 1) {\n // Single match - use it directly\n el = allMatches[0];\n } else {\n // Multiple matches without index - find first visible one\n el = allMatches[0];\n for (const candidate of allMatches) {\n const style = candidate.ownerDocument.defaultView.getComputedStyle(candidate);\n const rect = candidate.getBoundingClientRect();\n \n const hasSize = rect.width > 0 && rect.height > 0;\n const isDisplayed = style.display !== 'none' && style.visibility !== 'hidden';\n const isOpaque = parseFloat(style.opacity) > 0;\n const isPositioned = candidate.offsetParent !== null || style.position === 'fixed';\n \n const isVisible = hasSize && isDisplayed && isOpaque && isPositioned;\n \n if (isVisible) {\n el = candidate;\n break;\n }\n }\n }\n \n const tagName = el.tagName.toLowerCase();\n if (tagName === 'option') {\n const quote = (text) => \"'\" + String(text).split(\"'\").join(\"'\\\\''\") + \"'\";\n const select = el.closest('select');\n const target = select && select.id ? '#' + select.id : select && select.name ? 'select[name=\"' + select.name + '\"]' : 'select';\n return {\n success: false,\n error: 'An <option> is chosen through its <select>, not clicked',\n selector: selector,\n elementType: tagName,\n exitCode: 81,\n suggestion: 'bdg dom fill ' + quote(target) + ' ' + quote(el.value || el.text.trim())\n };\n }\n // Disabled elements still get hover (tooltips often explain why)\n if (action !== 'hover' && (el.disabled || el.matches(':disabled'))) {\n return {\n success: false,\n error: 'Element is disabled',\n selector: selector,\n elementType: tagName,\n suggestion: 'A user cannot click a disabled element; enable it first (it may depend on other fields)'\n };\n }\n el.scrollIntoView({ behavior: 'auto', block: 'center' });\n\n // The caller clicks with real mouse events at (x, y) when the element is the\n // topmost thing there; otherwise it falls back to el.click() via this handle.\n // Hit-testing happens in the element's own root (shadow root or frame\n // document), then in each enclosing document at the frame's position, so an\n // overlay over the iframe counts as covering it. The mouse events need\n // top-page coordinates: frame offsets (border and padding) are added.\n window.__bdgClickTarget = el;\n const rect = el.getBoundingClientRect();\n const view = el.ownerDocument.defaultView;\n const hitTest = (node, px, py) => {\n const root = node.getRootNode();\n const hit = (typeof root.elementFromPoint === 'function' ? root : node.ownerDocument).elementFromPoint(px, py);\n return hit !== null && (hit === node || node.contains(hit));\n };\n const toTopPage = (px, py) => {\n let hittable = hitTest(el, px, py);\n for (let frameWindow = view; frameWindow && frameWindow.frameElement; frameWindow = frameWindow.parent) {\n const frame = frameWindow.frameElement;\n const frameRect = frame.getBoundingClientRect();\n const frameStyle = frame.ownerDocument.defaultView.getComputedStyle(frame);\n px += frameRect.left + frame.clientLeft + parseFloat(frameStyle.paddingLeft);\n py += frameRect.top + frame.clientTop + parseFloat(frameStyle.paddingTop);\n hittable = hittable && hitTest(frame, px, py);\n }\n return { x: px, y: py, hittable: hittable };\n };\n const left = Math.max(rect.left, 0);\n const right = Math.min(rect.right, view.innerWidth);\n const top = Math.max(rect.top, 0);\n const bottom = Math.min(rect.bottom, view.innerHeight);\n const area = right > left && bottom > top\n ? { left: left, top: top, width: right - left, height: bottom - top }\n : { left: rect.left, top: rect.top, width: rect.width, height: rect.height };\n const fractions = [[0.5, 0.5], [0.5, 0.25], [0.5, 0.75], [0.25, 0.5], [0.75, 0.5], [0.25, 0.25], [0.75, 0.25], [0.25, 0.75], [0.75, 0.75]];\n const hasSize = rect.width > 0 && rect.height > 0;\n let point = null;\n for (const [fx, fy] of hasSize ? fractions : [[0.5, 0.5]]) {\n const candidate = toTopPage(area.left + area.width * fx, area.top + area.height * fy);\n point = point || candidate;\n if (hasSize && candidate.hittable) { point = candidate; break; }\n }\n const x = point.x;\n const y = point.y;\n const hittable = hasSize && point.hittable;\n const describe = (node) => node.tagName.toLowerCase() + (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : '');\n const coveredBy = () => {\n const root = el.getRootNode();\n const hit = (typeof root.elementFromPoint === 'function' ? root : el.ownerDocument)\n .elementFromPoint(area.left + area.width / 2, area.top + area.height / 2);\n return hit && !el.contains(hit) ? ' (' + describe(hit) + ')' : '';\n };\n const style = view.getComputedStyle(el);\n let obstruction = null;\n if (style.display === 'none' || el.getClientRects().length === 0) obstruction = 'not rendered (display: none)';\n else if (style.visibility === 'hidden') obstruction = 'hidden (visibility: hidden)';\n else if (el.closest('[inert]')) obstruction = 'inert (the page made it non-interactive)';\n else if (style.pointerEvents === 'none') obstruction = 'not clickable (pointer-events: none)';\n else if (!hasSize) obstruction = 'zero-size';\n else if (!hittable) obstruction = 'covered by another element' + coveredBy();\n\n return {\n success: true,\n selector: selector,\n elementType: tagName,\n matchCount: allMatches.length,\n selectedIndex: typeof index === 'number' ? index : undefined,\n x: x,\n y: y,\n hittable: hittable,\n obstruction: obstruction\n };\n})\n";
90
+ export declare const CLICK_ELEMENT_SCRIPT: string;
35
91
  /**
36
92
  * Options for filling an element.
37
93
  */