@midscene/shared 1.5.4-beta-20260310084708.0 → 1.5.4

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.
@@ -25,6 +25,7 @@ var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  getNodeInfoByXpath: ()=>getNodeInfoByXpath,
28
+ getXpathsById: ()=>getXpathsById,
28
29
  getElementXpath: ()=>getElementXpath,
29
30
  getElementInfoByXpath: ()=>getElementInfoByXpath,
30
31
  getXpathsByPoint: ()=>getXpathsByPoint
@@ -32,6 +33,54 @@ __webpack_require__.d(__webpack_exports__, {
32
33
  const external_dom_util_js_namespaceObject = require("./dom-util.js");
33
34
  const external_util_js_namespaceObject = require("./util.js");
34
35
  const external_web_extractor_js_namespaceObject = require("./web-extractor.js");
36
+ const SUB_XPATH_SEPARATOR = '|>>|';
37
+ function parseCSSZoom(style) {
38
+ return Number.parseFloat(style.zoom ?? '1') || 1;
39
+ }
40
+ function calculateIframeOffset(nodeOwnerDoc, rootDoc) {
41
+ let leftOffset = 0;
42
+ let topOffset = 0;
43
+ let iterDoc = nodeOwnerDoc;
44
+ while(iterDoc && iterDoc !== rootDoc)try {
45
+ const frameElement = iterDoc.defaultView?.frameElement;
46
+ if (!frameElement) break;
47
+ const rect = frameElement.getBoundingClientRect();
48
+ const parentWin = iterDoc.defaultView?.parent;
49
+ let borderLeft = 0;
50
+ let borderTop = 0;
51
+ let zoom = 1;
52
+ try {
53
+ if (parentWin) {
54
+ const style = parentWin.getComputedStyle(frameElement);
55
+ borderLeft = Number.parseFloat(style.borderLeftWidth) || 0;
56
+ borderTop = Number.parseFloat(style.borderTopWidth) || 0;
57
+ zoom = parseCSSZoom(style);
58
+ }
59
+ } catch {}
60
+ leftOffset = leftOffset / zoom + rect.left + borderLeft;
61
+ topOffset = topOffset / zoom + rect.top + borderTop;
62
+ iterDoc = frameElement.ownerDocument;
63
+ } catch {
64
+ break;
65
+ }
66
+ return {
67
+ left: leftOffset,
68
+ top: topOffset
69
+ };
70
+ }
71
+ function translatePointToIframeCoordinates(point, iframeElement, parentWindow) {
72
+ const rect = iframeElement.getBoundingClientRect();
73
+ const style = parentWindow.getComputedStyle(iframeElement);
74
+ const clientLeft = iframeElement.clientLeft;
75
+ const clientTop = iframeElement.clientTop;
76
+ const paddingLeft = Number.parseFloat(style.paddingLeft) || 0;
77
+ const paddingTop = Number.parseFloat(style.paddingTop) || 0;
78
+ const zoom = parseCSSZoom(style);
79
+ return {
80
+ left: (point.left - rect.left - clientLeft - paddingLeft) / zoom,
81
+ top: (point.top - rect.top - clientTop - paddingTop) / zoom
82
+ };
83
+ }
35
84
  const getElementXpathIndex = (element)=>{
36
85
  let index = 1;
37
86
  let prev = element.previousElementSibling;
@@ -45,8 +94,8 @@ const normalizeXpathText = (text)=>{
45
94
  if ('string' != typeof text) return '';
46
95
  return text.replace(/\s+/g, ' ').trim();
47
96
  };
48
- const buildCurrentElementXpath = (element, isOrderSensitive, isLeafElement)=>{
49
- const parentPath = element.parentNode ? getElementXpath(element.parentNode, isOrderSensitive) : '';
97
+ const buildCurrentElementXpath = (element, isOrderSensitive, isLeafElement, limitToCurrentDocument = false)=>{
98
+ const parentPath = element.parentNode ? getElementXpath(element.parentNode, isOrderSensitive, false, limitToCurrentDocument) : '';
50
99
  const prefix = parentPath ? `${parentPath}/` : '/';
51
100
  const tagName = element.nodeName.toLowerCase();
52
101
  const textContent = element.textContent?.trim();
@@ -60,11 +109,11 @@ const buildCurrentElementXpath = (element, isOrderSensitive, isLeafElement)=>{
60
109
  const index = getElementXpathIndex(element);
61
110
  return `${prefix}${tagSelector}[${index}]`;
62
111
  };
63
- const getElementXpath = (element, isOrderSensitive = false, isLeafElement = false)=>{
112
+ const getElementXpath = (element, isOrderSensitive = false, isLeafElement = false, limitToCurrentDocument = false)=>{
64
113
  if (element.nodeType === Node.TEXT_NODE) {
65
114
  const parentNode = element.parentNode;
66
115
  if (parentNode && parentNode.nodeType === Node.ELEMENT_NODE) {
67
- const parentXPath = getElementXpath(parentNode, isOrderSensitive, true);
116
+ const parentXPath = getElementXpath(parentNode, isOrderSensitive, true, limitToCurrentDocument);
68
117
  const textContent = element.textContent?.trim();
69
118
  if (textContent) return `${parentXPath}/text()[normalize-space()="${normalizeXpathText(textContent)}"]`;
70
119
  return `${parentXPath}/text()`;
@@ -73,68 +122,173 @@ const getElementXpath = (element, isOrderSensitive = false, isLeafElement = fals
73
122
  }
74
123
  if (element.nodeType !== Node.ELEMENT_NODE) return '';
75
124
  const el = element;
76
- if (el === document.documentElement) return '/html';
77
- if (el === document.body) return '/html/body';
125
+ try {
126
+ const nodeName = el.nodeName.toLowerCase();
127
+ if (el === el.ownerDocument?.documentElement || 'html' === nodeName) {
128
+ if (!limitToCurrentDocument) {
129
+ const frameElement = el.ownerDocument?.defaultView?.frameElement;
130
+ if (frameElement) {
131
+ const framePath = getElementXpath(frameElement, isOrderSensitive, false, limitToCurrentDocument);
132
+ return `${framePath}${SUB_XPATH_SEPARATOR}/html`;
133
+ }
134
+ }
135
+ return '/html';
136
+ }
137
+ if (el === el.ownerDocument?.body || 'body' === nodeName) {
138
+ if (!limitToCurrentDocument) {
139
+ const frameElement = el.ownerDocument?.defaultView?.frameElement;
140
+ if (frameElement) {
141
+ const framePath = getElementXpath(frameElement, isOrderSensitive, false, limitToCurrentDocument);
142
+ return `${framePath}${SUB_XPATH_SEPARATOR}/html/body`;
143
+ }
144
+ }
145
+ return '/html/body';
146
+ }
147
+ } catch (error) {
148
+ (0, external_util_js_namespaceObject.logger)('[midscene:locator] ownerDocument access failed:', error);
149
+ if ('html' === el.nodeName.toLowerCase()) return '/html';
150
+ if ('body' === el.nodeName.toLowerCase()) return '/html/body';
151
+ }
78
152
  if ((0, external_dom_util_js_namespaceObject.isSvgElement)(el)) {
79
153
  const tagName = el.nodeName.toLowerCase();
80
- if ('svg' === tagName) return buildCurrentElementXpath(el, isOrderSensitive, isLeafElement);
154
+ if ('svg' === tagName) return buildCurrentElementXpath(el, isOrderSensitive, isLeafElement, limitToCurrentDocument);
81
155
  let parent = el.parentNode;
82
156
  while(parent && parent.nodeType === Node.ELEMENT_NODE){
83
157
  const parentEl = parent;
84
- if (!(0, external_dom_util_js_namespaceObject.isSvgElement)(parentEl)) return getElementXpath(parentEl, isOrderSensitive, isLeafElement);
85
- {
86
- const parentTag = parentEl.nodeName.toLowerCase();
87
- if ('svg' === parentTag) return getElementXpath(parentEl, isOrderSensitive, isLeafElement);
88
- }
158
+ if (!(0, external_dom_util_js_namespaceObject.isSvgElement)(parentEl)) return getElementXpath(parentEl, isOrderSensitive, isLeafElement, limitToCurrentDocument);
159
+ const parentTag = parentEl.nodeName.toLowerCase();
160
+ if ('svg' === parentTag) return getElementXpath(parentEl, isOrderSensitive, isLeafElement, limitToCurrentDocument);
89
161
  parent = parent.parentNode;
90
162
  }
91
163
  const fallbackParent = el.parentNode;
92
- if (fallbackParent && fallbackParent.nodeType === Node.ELEMENT_NODE) return getElementXpath(fallbackParent, isOrderSensitive, isLeafElement);
164
+ if (fallbackParent && fallbackParent.nodeType === Node.ELEMENT_NODE) return getElementXpath(fallbackParent, isOrderSensitive, isLeafElement, limitToCurrentDocument);
93
165
  return '';
94
166
  }
95
- return buildCurrentElementXpath(el, isOrderSensitive, isLeafElement);
167
+ return buildCurrentElementXpath(el, isOrderSensitive, isLeafElement, limitToCurrentDocument);
96
168
  };
97
- function getXpathsByPoint(point, isOrderSensitive) {
98
- const element = document.elementFromPoint(point.left, point.top);
99
- if (!element) return null;
100
- const fullXPath = getElementXpath(element, isOrderSensitive, true);
169
+ function getXpathsById(id) {
170
+ const node = (0, external_util_js_namespaceObject.getNodeFromCacheList)(id);
171
+ if (!node) return null;
172
+ const fullXPath = getElementXpath(node, false, true, true);
101
173
  return [
102
174
  fullXPath
103
175
  ];
104
176
  }
177
+ function getXpathsByPoint(point, isOrderSensitive) {
178
+ let currentWindow = 'undefined' != typeof window ? window : void 0;
179
+ let currentDocument = 'undefined' != typeof document ? document : void 0;
180
+ let { left, top } = point;
181
+ let depth = 0;
182
+ const MAX_DEPTH = 10;
183
+ let xpathPrefix = '';
184
+ let lastFoundElement = null;
185
+ while(depth < MAX_DEPTH){
186
+ depth++;
187
+ const element = currentDocument.elementFromPoint(left, top);
188
+ if (!element) {
189
+ if (lastFoundElement) {
190
+ const fullXPath = getElementXpath(lastFoundElement, isOrderSensitive, true, true);
191
+ return [
192
+ xpathPrefix + fullXPath
193
+ ];
194
+ }
195
+ return null;
196
+ }
197
+ lastFoundElement = element;
198
+ const tag = element.tagName.toLowerCase();
199
+ if ('iframe' === tag || 'frame' === tag) try {
200
+ const contentWindow = element.contentWindow;
201
+ const contentDocument = element.contentDocument;
202
+ if (contentWindow && contentDocument) {
203
+ const localPoint = translatePointToIframeCoordinates({
204
+ left,
205
+ top
206
+ }, element, currentWindow);
207
+ const currentIframeXpath = getElementXpath(element, isOrderSensitive, false, true);
208
+ xpathPrefix += currentIframeXpath + SUB_XPATH_SEPARATOR;
209
+ currentWindow = contentWindow;
210
+ currentDocument = contentDocument;
211
+ left = localPoint.left;
212
+ top = localPoint.top;
213
+ continue;
214
+ }
215
+ } catch (error) {
216
+ (0, external_util_js_namespaceObject.logger)('[midscene:locator] iframe penetration failed (cross-origin?):', error);
217
+ }
218
+ const fullXPath = getElementXpath(element, isOrderSensitive, true, true);
219
+ return [
220
+ xpathPrefix + fullXPath
221
+ ];
222
+ }
223
+ if (lastFoundElement) {
224
+ const fullXPath = getElementXpath(lastFoundElement, isOrderSensitive, true, true);
225
+ return [
226
+ xpathPrefix + fullXPath
227
+ ];
228
+ }
229
+ return null;
230
+ }
105
231
  function getNodeInfoByXpath(xpath) {
106
- const xpathResult = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
107
- if (1 !== xpathResult.snapshotLength) {
108
- console.warn(`[midscene:warning] Received XPath "${xpath}" but it matched ${xpathResult.snapshotLength} elements. Discarding this result.`);
109
- return null;
232
+ const parts = xpath.split(SUB_XPATH_SEPARATOR).map((p)=>p.trim()).filter(Boolean);
233
+ if (0 === parts.length) return null;
234
+ let currentDocument = 'undefined' != typeof document ? document : void 0;
235
+ let node = null;
236
+ for(let i = 0; i < parts.length; i++){
237
+ const currentXpath = parts[i];
238
+ const xpathResult = currentDocument.evaluate(currentXpath, currentDocument, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
239
+ if (1 !== xpathResult.snapshotLength) {
240
+ (0, external_util_js_namespaceObject.logger)(`[midscene:locator] XPath "${currentXpath}" matched ${xpathResult.snapshotLength} elements (expected 1), discarding.`);
241
+ return null;
242
+ }
243
+ node = xpathResult.snapshotItem(0);
244
+ if (i < parts.length - 1) if (!node || node.nodeType !== Node.ELEMENT_NODE || 'iframe' !== node.tagName.toLowerCase()) return null;
245
+ else try {
246
+ const contentDocument = node.contentDocument;
247
+ if (contentDocument) currentDocument = contentDocument;
248
+ else {
249
+ (0, external_util_js_namespaceObject.logger)('[midscene:locator] iframe contentDocument is null (cross-origin?)');
250
+ return null;
251
+ }
252
+ } catch (error) {
253
+ (0, external_util_js_namespaceObject.logger)('[midscene:locator] iframe contentDocument access failed:', error);
254
+ return null;
255
+ }
110
256
  }
111
- const node = xpathResult.snapshotItem(0);
112
257
  return node;
113
258
  }
114
259
  function getElementInfoByXpath(xpath) {
115
260
  const node = getNodeInfoByXpath(xpath);
116
261
  if (!node) return null;
117
- if (node instanceof Element) {
118
- const rect = (0, external_util_js_namespaceObject.getRect)(node, 1, window);
119
- const isVisible = (0, external_util_js_namespaceObject.isElementPartiallyInViewport)(rect, window, document, 1);
262
+ let targetWindow = 'undefined' != typeof window ? window : void 0;
263
+ let targetDocument = 'undefined' != typeof document ? document : void 0;
264
+ if (node.ownerDocument?.defaultView) {
265
+ targetWindow = node.ownerDocument.defaultView;
266
+ targetDocument = node.ownerDocument;
267
+ }
268
+ const rootDoc = 'undefined' != typeof document ? document : null;
269
+ const iframeOffset = calculateIframeOffset(node.ownerDocument ?? null, rootDoc);
270
+ const targetWin = targetWindow;
271
+ const targetDoc = targetDocument;
272
+ if (node instanceof targetWin.HTMLElement) {
273
+ const rect = (0, external_util_js_namespaceObject.getRect)(node, 1, targetWin);
274
+ const isVisible = (0, external_util_js_namespaceObject.isElementPartiallyInViewport)(rect, targetWin, targetDoc, 1);
120
275
  if (!isVisible) node.scrollIntoView({
121
276
  behavior: 'instant',
122
277
  block: 'center'
123
278
  });
124
279
  }
125
- return (0, external_web_extractor_js_namespaceObject.collectElementInfo)(node, window, document, 1, {
126
- left: 0,
127
- top: 0
128
- }, true);
280
+ return (0, external_web_extractor_js_namespaceObject.collectElementInfo)(node, targetWin, targetDoc, 1, iframeOffset, true);
129
281
  }
130
282
  exports.getElementInfoByXpath = __webpack_exports__.getElementInfoByXpath;
131
283
  exports.getElementXpath = __webpack_exports__.getElementXpath;
132
284
  exports.getNodeInfoByXpath = __webpack_exports__.getNodeInfoByXpath;
285
+ exports.getXpathsById = __webpack_exports__.getXpathsById;
133
286
  exports.getXpathsByPoint = __webpack_exports__.getXpathsByPoint;
134
287
  for(var __rspack_i in __webpack_exports__)if (-1 === [
135
288
  "getElementInfoByXpath",
136
289
  "getElementXpath",
137
290
  "getNodeInfoByXpath",
291
+ "getXpathsById",
138
292
  "getXpathsByPoint"
139
293
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
140
294
  Object.defineProperty(exports, '__esModule', {
@@ -26,6 +26,7 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  setDebugMode: ()=>setDebugMode,
28
28
  getNodeAttributes: ()=>getNodeAttributes,
29
+ getNodeFromCacheList: ()=>getNodeFromCacheList,
29
30
  hasOverflowY: ()=>hasOverflowY,
30
31
  setExtractTextWithPositionOnWindow: ()=>setExtractTextWithPositionOnWindow,
31
32
  setGenerateHashOnWindow: ()=>setGenerateHashOnWindow,
@@ -40,7 +41,9 @@ __webpack_require__.d(__webpack_exports__, {
40
41
  setMidsceneVisibleRectOnWindow: ()=>setMidsceneVisibleRectOnWindow,
41
42
  getRect: ()=>getRect,
42
43
  generateId: ()=>generateId,
43
- validTextNodeContent: ()=>validTextNodeContent
44
+ setNodeToCacheList: ()=>setNodeToCacheList,
45
+ validTextNodeContent: ()=>validTextNodeContent,
46
+ setNodeHashCacheListOnWindow: ()=>setNodeHashCacheListOnWindow
44
47
  });
45
48
  const external_utils_js_namespaceObject = require("../utils.js");
46
49
  const external_web_extractor_js_namespaceObject = require("./web-extractor.js");
@@ -241,8 +244,33 @@ function getNodeAttributes(node, currentWindow) {
241
244
  });
242
245
  return Object.fromEntries(attributesList);
243
246
  }
247
+ const NODE_CACHE_MAX_SIZE = 2000;
248
+ function setNodeHashCacheListOnWindow() {
249
+ if ('undefined' != typeof window) window.midsceneNodeHashCache = new Map();
250
+ }
251
+ function getNodeCacheMap() {
252
+ if ('undefined' == typeof window) return;
253
+ return window.midsceneNodeHashCache;
254
+ }
255
+ function setNodeToCacheList(node, id) {
256
+ const cache = getNodeCacheMap();
257
+ if (!cache) return;
258
+ if (cache.has(id)) return;
259
+ if (cache.size >= NODE_CACHE_MAX_SIZE) {
260
+ const firstKey = cache.keys().next().value;
261
+ if (void 0 !== firstKey) cache.delete(firstKey);
262
+ }
263
+ cache.set(id, node);
264
+ }
265
+ function getNodeFromCacheList(id) {
266
+ return getNodeCacheMap()?.get(id);
267
+ }
244
268
  function midsceneGenerateHash(node, content, rect) {
245
269
  const slicedHash = (0, external_utils_js_namespaceObject.generateHashId)(rect, content);
270
+ if (node) {
271
+ if ('undefined' != typeof window && !getNodeCacheMap()) setNodeHashCacheListOnWindow();
272
+ setNodeToCacheList(node, slicedHash);
273
+ }
246
274
  return slicedHash;
247
275
  }
248
276
  function generateId(numberId) {
@@ -265,6 +293,7 @@ exports.elementRect = __webpack_exports__.elementRect;
265
293
  exports.generateId = __webpack_exports__.generateId;
266
294
  exports.getDebugMode = __webpack_exports__.getDebugMode;
267
295
  exports.getNodeAttributes = __webpack_exports__.getNodeAttributes;
296
+ exports.getNodeFromCacheList = __webpack_exports__.getNodeFromCacheList;
268
297
  exports.getPseudoElementContent = __webpack_exports__.getPseudoElementContent;
269
298
  exports.getRect = __webpack_exports__.getRect;
270
299
  exports.getTopDocument = __webpack_exports__.getTopDocument;
@@ -277,12 +306,15 @@ exports.setDebugMode = __webpack_exports__.setDebugMode;
277
306
  exports.setExtractTextWithPositionOnWindow = __webpack_exports__.setExtractTextWithPositionOnWindow;
278
307
  exports.setGenerateHashOnWindow = __webpack_exports__.setGenerateHashOnWindow;
279
308
  exports.setMidsceneVisibleRectOnWindow = __webpack_exports__.setMidsceneVisibleRectOnWindow;
309
+ exports.setNodeHashCacheListOnWindow = __webpack_exports__.setNodeHashCacheListOnWindow;
310
+ exports.setNodeToCacheList = __webpack_exports__.setNodeToCacheList;
280
311
  exports.validTextNodeContent = __webpack_exports__.validTextNodeContent;
281
312
  for(var __rspack_i in __webpack_exports__)if (-1 === [
282
313
  "elementRect",
283
314
  "generateId",
284
315
  "getDebugMode",
285
316
  "getNodeAttributes",
317
+ "getNodeFromCacheList",
286
318
  "getPseudoElementContent",
287
319
  "getRect",
288
320
  "getTopDocument",
@@ -295,6 +327,8 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
295
327
  "setExtractTextWithPositionOnWindow",
296
328
  "setGenerateHashOnWindow",
297
329
  "setMidsceneVisibleRectOnWindow",
330
+ "setNodeHashCacheListOnWindow",
331
+ "setNodeToCacheList",
298
332
  "validTextNodeContent"
299
333
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
300
334
  Object.defineProperty(exports, '__esModule', {