@sqaitech/shared 0.5.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 (169) hide show
  1. package/README.md +9 -0
  2. package/dist/es/baseDB.mjs +109 -0
  3. package/dist/es/build/copy-static.mjs +29 -0
  4. package/dist/es/common.mjs +37 -0
  5. package/dist/es/constants/example-code.mjs +202 -0
  6. package/dist/es/constants/index.mjs +24 -0
  7. package/dist/es/env/basic.mjs +6 -0
  8. package/dist/es/env/constants.mjs +97 -0
  9. package/dist/es/env/decide-model-config.mjs +172 -0
  10. package/dist/es/env/global-config-manager.mjs +91 -0
  11. package/dist/es/env/helper.mjs +48 -0
  12. package/dist/es/env/index.mjs +5 -0
  13. package/dist/es/env/init-debug.mjs +18 -0
  14. package/dist/es/env/model-config-manager.mjs +99 -0
  15. package/dist/es/env/parse.mjs +69 -0
  16. package/dist/es/env/types.mjs +253 -0
  17. package/dist/es/env/utils.mjs +18 -0
  18. package/dist/es/extractor/constants.mjs +2 -0
  19. package/dist/es/extractor/debug.mjs +6 -0
  20. package/dist/es/extractor/dom-util.mjs +92 -0
  21. package/dist/es/extractor/index.mjs +6 -0
  22. package/dist/es/extractor/locator.mjs +95 -0
  23. package/dist/es/extractor/tree.mjs +81 -0
  24. package/dist/es/extractor/util.mjs +244 -0
  25. package/dist/es/extractor/web-extractor.mjs +310 -0
  26. package/dist/es/img/box-select.mjs +184 -0
  27. package/dist/es/img/draw-box.mjs +42 -0
  28. package/dist/es/img/get-jimp.mjs +10 -0
  29. package/dist/es/img/get-photon.mjs +19 -0
  30. package/dist/es/img/get-sharp.mjs +11 -0
  31. package/dist/es/img/index.mjs +5 -0
  32. package/dist/es/img/info.mjs +32 -0
  33. package/dist/es/img/transform.mjs +192 -0
  34. package/dist/es/index.mjs +3 -0
  35. package/dist/es/logger.mjs +61 -0
  36. package/dist/es/node/fs.mjs +44 -0
  37. package/dist/es/node/index.mjs +1 -0
  38. package/dist/es/polyfills/async-hooks.mjs +2 -0
  39. package/dist/es/polyfills/index.mjs +1 -0
  40. package/dist/es/types/index.mjs +3 -0
  41. package/dist/es/us-keyboard-layout.mjs +1414 -0
  42. package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
  43. package/dist/es/utils.mjs +66 -0
  44. package/dist/lib/baseDB.js +149 -0
  45. package/dist/lib/build/copy-static.js +77 -0
  46. package/dist/lib/common.js +93 -0
  47. package/dist/lib/constants/example-code.js +239 -0
  48. package/dist/lib/constants/index.js +100 -0
  49. package/dist/lib/env/basic.js +40 -0
  50. package/dist/lib/env/constants.js +143 -0
  51. package/dist/lib/env/decide-model-config.js +212 -0
  52. package/dist/lib/env/global-config-manager.js +125 -0
  53. package/dist/lib/env/helper.js +89 -0
  54. package/dist/lib/env/index.js +94 -0
  55. package/dist/lib/env/init-debug.js +52 -0
  56. package/dist/lib/env/model-config-manager.js +133 -0
  57. package/dist/lib/env/parse.js +106 -0
  58. package/dist/lib/env/types.js +635 -0
  59. package/dist/lib/env/utils.js +61 -0
  60. package/dist/lib/extractor/constants.js +42 -0
  61. package/dist/lib/extractor/debug.js +12 -0
  62. package/dist/lib/extractor/dom-util.js +150 -0
  63. package/dist/lib/extractor/index.js +88 -0
  64. package/dist/lib/extractor/locator.js +141 -0
  65. package/dist/lib/extractor/tree.js +127 -0
  66. package/dist/lib/extractor/util.js +335 -0
  67. package/dist/lib/extractor/web-extractor.js +356 -0
  68. package/dist/lib/img/box-select.js +232 -0
  69. package/dist/lib/img/draw-box.js +89 -0
  70. package/dist/lib/img/get-jimp.js +72 -0
  71. package/dist/lib/img/get-photon.js +76 -0
  72. package/dist/lib/img/get-sharp.js +63 -0
  73. package/dist/lib/img/index.js +102 -0
  74. package/dist/lib/img/info.js +86 -0
  75. package/dist/lib/img/transform.js +279 -0
  76. package/dist/lib/index.js +43 -0
  77. package/dist/lib/logger.js +114 -0
  78. package/dist/lib/node/fs.js +97 -0
  79. package/dist/lib/node/index.js +60 -0
  80. package/dist/lib/polyfills/async-hooks.js +36 -0
  81. package/dist/lib/polyfills/index.js +60 -0
  82. package/dist/lib/types/index.js +37 -0
  83. package/dist/lib/us-keyboard-layout.js +1457 -0
  84. package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
  85. package/dist/lib/utils.js +136 -0
  86. package/dist/types/baseDB.d.ts +25 -0
  87. package/dist/types/build/copy-static.d.ts +31 -0
  88. package/dist/types/common.d.ts +12 -0
  89. package/dist/types/constants/example-code.d.ts +2 -0
  90. package/dist/types/constants/index.d.ts +22 -0
  91. package/dist/types/env/basic.d.ts +6 -0
  92. package/dist/types/env/constants.d.ts +40 -0
  93. package/dist/types/env/decide-model-config.d.ts +14 -0
  94. package/dist/types/env/global-config-manager.d.ts +32 -0
  95. package/dist/types/env/helper.d.ts +6 -0
  96. package/dist/types/env/index.d.ts +4 -0
  97. package/dist/types/env/init-debug.d.ts +1 -0
  98. package/dist/types/env/model-config-manager.d.ts +24 -0
  99. package/dist/types/env/parse.d.ts +12 -0
  100. package/dist/types/env/types.d.ts +294 -0
  101. package/dist/types/env/utils.d.ts +7 -0
  102. package/dist/types/extractor/constants.d.ts +1 -0
  103. package/dist/types/extractor/debug.d.ts +1 -0
  104. package/dist/types/extractor/dom-util.d.ts +26 -0
  105. package/dist/types/extractor/index.d.ts +33 -0
  106. package/dist/types/extractor/locator.d.ts +7 -0
  107. package/dist/types/extractor/tree.d.ts +9 -0
  108. package/dist/types/extractor/util.d.ts +43 -0
  109. package/dist/types/extractor/web-extractor.d.ts +19 -0
  110. package/dist/types/img/box-select.d.ts +25 -0
  111. package/dist/types/img/draw-box.d.ts +15 -0
  112. package/dist/types/img/get-jimp.d.ts +2 -0
  113. package/dist/types/img/get-photon.d.ts +8 -0
  114. package/dist/types/img/get-sharp.d.ts +3 -0
  115. package/dist/types/img/index.d.ts +4 -0
  116. package/dist/types/img/info.d.ts +29 -0
  117. package/dist/types/img/transform.d.ts +88 -0
  118. package/dist/types/index.d.ts +3 -0
  119. package/dist/types/logger.d.ts +4 -0
  120. package/dist/types/node/fs.d.ts +15 -0
  121. package/dist/types/node/index.d.ts +1 -0
  122. package/dist/types/polyfills/async-hooks.d.ts +6 -0
  123. package/dist/types/polyfills/index.d.ts +4 -0
  124. package/dist/types/types/index.d.ts +34 -0
  125. package/dist/types/us-keyboard-layout.d.ts +32 -0
  126. package/dist/types/utils.d.ts +22 -0
  127. package/package.json +106 -0
  128. package/src/baseDB.ts +158 -0
  129. package/src/build/copy-static.ts +62 -0
  130. package/src/common.ts +67 -0
  131. package/src/constants/example-code.ts +202 -0
  132. package/src/constants/index.ts +30 -0
  133. package/src/env/basic.ts +12 -0
  134. package/src/env/constants.ts +291 -0
  135. package/src/env/decide-model-config.ts +319 -0
  136. package/src/env/global-config-manager.ts +174 -0
  137. package/src/env/helper.ts +79 -0
  138. package/src/env/index.ts +4 -0
  139. package/src/env/init-debug.ts +29 -0
  140. package/src/env/model-config-manager.ts +145 -0
  141. package/src/env/parse.ts +131 -0
  142. package/src/env/types.ts +560 -0
  143. package/src/env/utils.ts +39 -0
  144. package/src/extractor/constants.ts +5 -0
  145. package/src/extractor/debug.ts +10 -0
  146. package/src/extractor/dom-util.ts +152 -0
  147. package/src/extractor/index.ts +50 -0
  148. package/src/extractor/locator.ts +179 -0
  149. package/src/extractor/tree.ts +179 -0
  150. package/src/extractor/util.ts +468 -0
  151. package/src/extractor/web-extractor.ts +481 -0
  152. package/src/img/box-select.ts +346 -0
  153. package/src/img/draw-box.ts +60 -0
  154. package/src/img/get-jimp.ts +12 -0
  155. package/src/img/get-photon.ts +48 -0
  156. package/src/img/get-sharp.ts +18 -0
  157. package/src/img/index.ts +24 -0
  158. package/src/img/info.ts +79 -0
  159. package/src/img/jimp.d.ts +4 -0
  160. package/src/img/transform.ts +396 -0
  161. package/src/index.ts +6 -0
  162. package/src/logger.ts +93 -0
  163. package/src/node/fs.ts +84 -0
  164. package/src/node/index.ts +1 -0
  165. package/src/polyfills/async-hooks.ts +6 -0
  166. package/src/polyfills/index.ts +4 -0
  167. package/src/types/index.ts +47 -0
  168. package/src/us-keyboard-layout.ts +723 -0
  169. package/src/utils.ts +127 -0
@@ -0,0 +1,481 @@
1
+ import {
2
+ CONTAINER_MINI_HEIGHT,
3
+ CONTAINER_MINI_WIDTH,
4
+ NodeType,
5
+ } from '../constants/index';
6
+ import type { WebElementInfo } from '../types';
7
+ import type { Point } from '../types';
8
+ import {
9
+ isAElement,
10
+ isButtonElement,
11
+ isContainerElement,
12
+ isFormElement,
13
+ isImgElement,
14
+ isTextElement,
15
+ } from './dom-util';
16
+ import { descriptionOfTree } from './tree';
17
+ import {
18
+ elementRect,
19
+ getNodeAttributes,
20
+ getPseudoElementContent,
21
+ getRect,
22
+ getTopDocument,
23
+ logger,
24
+ midsceneGenerateHash,
25
+ setDebugMode,
26
+ } from './util';
27
+
28
+ let indexId = 0;
29
+
30
+ function tagNameOfNode(node: globalThis.Node): string {
31
+ let tagName = '';
32
+ if (node instanceof HTMLElement) {
33
+ tagName = node.tagName?.toLowerCase();
34
+ } else {
35
+ const parentElement = node.parentElement;
36
+ if (parentElement && parentElement instanceof HTMLElement) {
37
+ tagName = parentElement.tagName?.toLowerCase();
38
+ }
39
+ }
40
+
41
+ return tagName ? `<${tagName}>` : '';
42
+ }
43
+
44
+ export function collectElementInfo(
45
+ node: Node,
46
+ currentWindow: typeof window,
47
+ currentDocument: typeof document,
48
+ baseZoom = 1,
49
+ basePoint: Point = { left: 0, top: 0 },
50
+ isContainer = false, // if true, the element will be considered as a container
51
+ ): WebElementInfo | null | any {
52
+ const rect = elementRect(node, currentWindow, currentDocument, baseZoom);
53
+
54
+ if (!rect) {
55
+ return null;
56
+ }
57
+
58
+ if (
59
+ rect.width < CONTAINER_MINI_WIDTH ||
60
+ rect.height < CONTAINER_MINI_HEIGHT
61
+ ) {
62
+ return null;
63
+ }
64
+
65
+ if (basePoint.left !== 0 || basePoint.top !== 0) {
66
+ rect.left += basePoint.left;
67
+ rect.top += basePoint.top;
68
+ }
69
+ // Skip elements that cover the entire viewport, as they are likely background containers
70
+ // rather than meaningful interactive elements
71
+ if (rect.height >= window.innerHeight && rect.width >= window.innerWidth) {
72
+ return null;
73
+ }
74
+
75
+ if (isFormElement(node)) {
76
+ const attributes = getNodeAttributes(node, currentWindow);
77
+ let valueContent =
78
+ attributes.value || attributes.placeholder || node.textContent || '';
79
+ const nodeHashId = midsceneGenerateHash(node, valueContent, rect);
80
+ const tagName = (node as HTMLElement).tagName.toLowerCase();
81
+ if ((node as HTMLElement).tagName.toLowerCase() === 'select') {
82
+ // Get the selected option using the selectedIndex property
83
+ const selectedOption = (node as HTMLSelectElement).options[
84
+ (node as HTMLSelectElement).selectedIndex
85
+ ];
86
+
87
+ // Retrieve the text content of the selected option
88
+ valueContent = selectedOption?.textContent || '';
89
+ }
90
+
91
+ if (
92
+ ((node as HTMLElement).tagName.toLowerCase() === 'input' ||
93
+ (node as HTMLElement).tagName.toLowerCase() === 'textarea') &&
94
+ (node as HTMLInputElement).value
95
+ ) {
96
+ valueContent = (node as HTMLInputElement).value;
97
+ }
98
+
99
+ const elementInfo: WebElementInfo = {
100
+ id: nodeHashId,
101
+ nodeHashId,
102
+ nodeType: NodeType.FORM_ITEM,
103
+ indexId: indexId++,
104
+ attributes: {
105
+ ...attributes,
106
+ htmlTagName: `<${tagName}>`,
107
+ nodeType: NodeType.FORM_ITEM,
108
+ },
109
+ content: valueContent.trim(),
110
+ rect,
111
+ center: [
112
+ Math.round(rect.left + rect.width / 2),
113
+ Math.round(rect.top + rect.height / 2),
114
+ ],
115
+ zoom: rect.zoom,
116
+ isVisible: rect.isVisible,
117
+ };
118
+ return elementInfo;
119
+ }
120
+
121
+ if (isButtonElement(node)) {
122
+ const rect = mergeElementAndChildrenRects(
123
+ node,
124
+ currentWindow,
125
+ currentDocument,
126
+ baseZoom,
127
+ );
128
+ if (!rect) {
129
+ return null;
130
+ }
131
+ const attributes = getNodeAttributes(node, currentWindow);
132
+ const pseudo = getPseudoElementContent(node, currentWindow);
133
+ const content = node.innerText || pseudo.before || pseudo.after || '';
134
+ const nodeHashId = midsceneGenerateHash(node, content, rect);
135
+ const elementInfo: WebElementInfo = {
136
+ id: nodeHashId,
137
+ indexId: indexId++,
138
+ nodeHashId,
139
+ nodeType: NodeType.BUTTON,
140
+ attributes: {
141
+ ...attributes,
142
+ htmlTagName: tagNameOfNode(node),
143
+ nodeType: NodeType.BUTTON,
144
+ },
145
+ content,
146
+ rect,
147
+ center: [
148
+ Math.round(rect.left + rect.width / 2),
149
+ Math.round(rect.top + rect.height / 2),
150
+ ],
151
+ zoom: rect.zoom,
152
+ isVisible: rect.isVisible,
153
+ };
154
+ return elementInfo;
155
+ }
156
+
157
+ if (isImgElement(node)) {
158
+ const attributes = getNodeAttributes(node, currentWindow);
159
+ const nodeHashId = midsceneGenerateHash(node, '', rect);
160
+ const elementInfo: WebElementInfo = {
161
+ id: nodeHashId,
162
+ indexId: indexId++,
163
+ nodeHashId,
164
+ attributes: {
165
+ ...attributes,
166
+ ...(node.nodeName?.toLowerCase() === 'svg'
167
+ ? {
168
+ svgContent: 'true',
169
+ }
170
+ : {}),
171
+ nodeType: NodeType.IMG,
172
+ htmlTagName: tagNameOfNode(node),
173
+ },
174
+ nodeType: NodeType.IMG,
175
+ content: '',
176
+ rect,
177
+ center: [
178
+ Math.round(rect.left + rect.width / 2),
179
+ Math.round(rect.top + rect.height / 2),
180
+ ],
181
+ zoom: rect.zoom,
182
+ isVisible: rect.isVisible,
183
+ };
184
+ return elementInfo;
185
+ }
186
+
187
+ if (isTextElement(node)) {
188
+ const text = node.textContent?.trim().replace(/\n+/g, ' ');
189
+ if (!text) {
190
+ return null;
191
+ }
192
+ const attributes = getNodeAttributes(node, currentWindow);
193
+ const attributeKeys = Object.keys(attributes);
194
+ if (!text.trim() && attributeKeys.length === 0) {
195
+ return null;
196
+ }
197
+ const nodeHashId = midsceneGenerateHash(node, text, rect);
198
+ const elementInfo: WebElementInfo = {
199
+ id: nodeHashId,
200
+ indexId: indexId++,
201
+ nodeHashId,
202
+ nodeType: NodeType.TEXT,
203
+ attributes: {
204
+ ...attributes,
205
+ nodeType: NodeType.TEXT,
206
+ htmlTagName: tagNameOfNode(node),
207
+ },
208
+ center: [
209
+ Math.round(rect.left + rect.width / 2),
210
+ Math.round(rect.top + rect.height / 2),
211
+ ],
212
+ content: text,
213
+ rect,
214
+ zoom: rect.zoom,
215
+ isVisible: rect.isVisible,
216
+ };
217
+ return elementInfo;
218
+ }
219
+
220
+ if (isAElement(node)) {
221
+ const attributes = getNodeAttributes(node, currentWindow);
222
+ const pseudo = getPseudoElementContent(node, currentWindow);
223
+ const content = node.innerText || pseudo.before || pseudo.after || '';
224
+ const nodeHashId = midsceneGenerateHash(node, content, rect);
225
+ const elementInfo: WebElementInfo = {
226
+ id: nodeHashId,
227
+ indexId: indexId++,
228
+ nodeHashId,
229
+ nodeType: NodeType.A,
230
+ attributes: {
231
+ ...attributes,
232
+ htmlTagName: tagNameOfNode(node),
233
+ nodeType: NodeType.A,
234
+ },
235
+ content,
236
+ rect,
237
+ center: [
238
+ Math.round(rect.left + rect.width / 2),
239
+ Math.round(rect.top + rect.height / 2),
240
+ ],
241
+ zoom: rect.zoom,
242
+ isVisible: rect.isVisible,
243
+ };
244
+ return elementInfo;
245
+ }
246
+
247
+ // else, consider as a container
248
+ if (isContainerElement(node) || isContainer) {
249
+ const attributes = getNodeAttributes(node, currentWindow);
250
+ const nodeHashId = midsceneGenerateHash(node, '', rect);
251
+ const elementInfo: WebElementInfo = {
252
+ id: nodeHashId,
253
+ nodeHashId,
254
+ indexId: indexId++,
255
+ nodeType: NodeType.CONTAINER,
256
+ attributes: {
257
+ ...attributes,
258
+ nodeType: NodeType.CONTAINER,
259
+ htmlTagName: tagNameOfNode(node),
260
+ },
261
+ content: '',
262
+ rect,
263
+ center: [
264
+ Math.round(rect.left + rect.width / 2),
265
+ Math.round(rect.top + rect.height / 2),
266
+ ],
267
+ zoom: rect.zoom,
268
+ isVisible: rect.isVisible,
269
+ };
270
+ return elementInfo;
271
+ }
272
+ return null;
273
+ }
274
+
275
+ interface WebElementNode {
276
+ node: WebElementInfo | null;
277
+ children: WebElementNode[];
278
+ }
279
+
280
+ // @deprecated
281
+ export function extractTextWithPosition(
282
+ initNode: globalThis.Node,
283
+ debugMode = false,
284
+ ): WebElementInfo[] {
285
+ const elementNode = extractTreeNode(initNode, debugMode);
286
+
287
+ // dfs topChildren
288
+ const elementInfoArray: WebElementInfo[] = [];
289
+ function dfsTopChildren(node: WebElementNode) {
290
+ if (node.node) {
291
+ elementInfoArray.push(node.node);
292
+ }
293
+ for (let i = 0; i < node.children.length; i++) {
294
+ dfsTopChildren(node.children[i]);
295
+ }
296
+ }
297
+ dfsTopChildren({ children: elementNode.children, node: elementNode.node });
298
+ return elementInfoArray;
299
+ }
300
+
301
+ export function extractTreeNodeAsString(
302
+ initNode: globalThis.Node,
303
+ visibleOnly = false,
304
+ debugMode = false,
305
+ ): string {
306
+ const elementNode = extractTreeNode(initNode, debugMode);
307
+ return descriptionOfTree(elementNode, undefined, false, visibleOnly);
308
+ }
309
+
310
+ export function extractTreeNode(
311
+ initNode: globalThis.Node,
312
+ debugMode = false,
313
+ ): WebElementNode {
314
+ setDebugMode(debugMode);
315
+ indexId = 0;
316
+
317
+ const topDocument = getTopDocument();
318
+ const startNode = initNode || topDocument;
319
+ const topChildren: WebElementNode[] = [];
320
+
321
+ function dfs(
322
+ node: globalThis.Node,
323
+ currentWindow: typeof globalThis.window,
324
+ currentDocument: typeof globalThis.document,
325
+ baseZoom = 1,
326
+ basePoint: Point = { left: 0, top: 0 },
327
+ ): WebElementNode | WebElementNode[] | null {
328
+ if (!node) {
329
+ return null;
330
+ }
331
+
332
+ if (node.nodeType && node.nodeType === 10) {
333
+ // Doctype node
334
+ return null;
335
+ }
336
+
337
+ const elementInfo = collectElementInfo(
338
+ node,
339
+ currentWindow,
340
+ currentDocument,
341
+ baseZoom,
342
+ basePoint,
343
+ );
344
+
345
+ if (node instanceof currentWindow.HTMLIFrameElement) {
346
+ if (
347
+ (node as HTMLIFrameElement).contentWindow &&
348
+ (node as HTMLIFrameElement).contentWindow
349
+ ) {
350
+ return null;
351
+ }
352
+ }
353
+
354
+ const nodeInfo: WebElementNode = {
355
+ node: elementInfo,
356
+ children: [],
357
+ };
358
+ // stop collecting if the node is a Button/Image/Text/FormItem/Container
359
+ if (
360
+ elementInfo?.nodeType === NodeType.BUTTON ||
361
+ elementInfo?.nodeType === NodeType.IMG ||
362
+ elementInfo?.nodeType === NodeType.TEXT ||
363
+ elementInfo?.nodeType === NodeType.FORM_ITEM ||
364
+ elementInfo?.nodeType === NodeType.CONTAINER // TODO: need return the container node?
365
+ ) {
366
+ return nodeInfo;
367
+ }
368
+
369
+ const rect = getRect(node, baseZoom, currentWindow);
370
+ for (let i = 0; i < node.childNodes.length; i++) {
371
+ logger('will dfs', node.childNodes[i]);
372
+ const childNodeInfo = dfs(
373
+ node.childNodes[i],
374
+ currentWindow,
375
+ currentDocument,
376
+ rect.zoom,
377
+ basePoint,
378
+ );
379
+ if (Array.isArray(childNodeInfo)) {
380
+ // if the recursive return is an array, expand and merge it into children
381
+ nodeInfo.children.push(...childNodeInfo);
382
+ } else if (childNodeInfo) {
383
+ nodeInfo.children.push(childNodeInfo);
384
+ }
385
+ }
386
+
387
+ // if nodeInfo.node is null
388
+ if (nodeInfo.node === null) {
389
+ if (nodeInfo.children.length === 0) {
390
+ return null;
391
+ }
392
+ // promote children to the upper layer
393
+ return nodeInfo.children;
394
+ }
395
+
396
+ return nodeInfo;
397
+ }
398
+
399
+ const rootNodeInfo = dfs(startNode, window, document, 1, {
400
+ left: 0,
401
+ top: 0,
402
+ });
403
+ if (Array.isArray(rootNodeInfo)) {
404
+ topChildren.push(...rootNodeInfo);
405
+ } else if (rootNodeInfo) {
406
+ topChildren.push(rootNodeInfo);
407
+ }
408
+ if (startNode === topDocument) {
409
+ // find all the same-origin iframes
410
+ const iframes = document.querySelectorAll('iframe');
411
+ for (let i = 0; i < iframes.length; i++) {
412
+ const iframe = iframes[i];
413
+ if (iframe.contentDocument && iframe.contentWindow) {
414
+ const iframeInfo = collectElementInfo(iframe, window, document, 1);
415
+ // when the iframe is in the viewport, we need to collect its children
416
+ if (iframeInfo) {
417
+ const iframeChildren = dfs(
418
+ iframe.contentDocument.body,
419
+ iframe.contentWindow as any,
420
+ iframe.contentDocument,
421
+ 1,
422
+ {
423
+ left: iframeInfo.rect.left,
424
+ top: iframeInfo.rect.top,
425
+ },
426
+ );
427
+ if (Array.isArray(iframeChildren)) {
428
+ topChildren.push(...iframeChildren);
429
+ } else if (iframeChildren) {
430
+ topChildren.push(iframeChildren);
431
+ }
432
+ }
433
+ }
434
+ }
435
+ }
436
+
437
+ return {
438
+ node: null,
439
+ children: topChildren,
440
+ };
441
+ }
442
+
443
+ export function mergeElementAndChildrenRects(
444
+ node: Node,
445
+ currentWindow: typeof window,
446
+ currentDocument: typeof document,
447
+ baseZoom = 1,
448
+ ) {
449
+ const selfRect = elementRect(node, currentWindow, currentDocument, baseZoom);
450
+ if (!selfRect) return null;
451
+
452
+ let minLeft = selfRect.left;
453
+ let minTop = selfRect.top;
454
+ let maxRight = selfRect.left + selfRect.width;
455
+ let maxBottom = selfRect.top + selfRect.height;
456
+
457
+ function traverse(child: Node) {
458
+ for (let i = 0; i < child.childNodes.length; i++) {
459
+ const sub = child.childNodes[i];
460
+ if (sub.nodeType === 1) {
461
+ const rect = elementRect(sub, currentWindow, currentDocument, baseZoom);
462
+ if (rect) {
463
+ minLeft = Math.min(minLeft, rect.left);
464
+ minTop = Math.min(minTop, rect.top);
465
+ maxRight = Math.max(maxRight, rect.left + rect.width);
466
+ maxBottom = Math.max(maxBottom, rect.top + rect.height);
467
+ }
468
+ traverse(sub);
469
+ }
470
+ }
471
+ }
472
+ traverse(node);
473
+
474
+ return {
475
+ ...selfRect,
476
+ left: minLeft,
477
+ top: minTop,
478
+ width: maxRight - minLeft,
479
+ height: maxBottom - minTop,
480
+ };
481
+ }