@xbrowser/cli 1.9.9 → 1.11.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 (27) hide show
  1. package/dist/{anti-bot-DR56Y63V.js → anti-bot-GTTYNEFB.js} +1 -1
  2. package/dist/{browser-FFYPFTBV.js → browser-Q5APBNF6.js} +1 -1
  3. package/dist/{browser-YCO4GJMZ.js → browser-V3JIWSTR.js} +2 -2
  4. package/dist/{browser-RHWUAMTB.js → browser-XNU7JQYC.js} +3 -2
  5. package/dist/{cdp-driver-J3YQ4LJV.js → cdp-driver-GD6YBBGE.js} +1 -1
  6. package/dist/cdp-driver-VEK6BNN6.js +49 -0
  7. package/dist/{cdp-driver-2T4P4ZE2.js → cdp-driver-XFTTZI5O.js} +436 -56
  8. package/dist/chunk-3FWLW7FS.js +106 -0
  9. package/dist/chunk-7OFM755Z.js +3509 -0
  10. package/dist/{chunk-5UGR6MUK.js → chunk-HBHOKZPN.js} +51 -16
  11. package/dist/{chunk-6OZWKXSW.js → chunk-HVPUVVSA.js} +72 -15
  12. package/dist/{chunk-GUTBIYFW.js → chunk-JEDP4PJW.js} +507 -70
  13. package/dist/{chunk-INTQPBYF.js → chunk-JKVUFP3G.js} +6 -2
  14. package/dist/{chunk-4452SPFI.js → chunk-NODRQGOK.js} +51 -16
  15. package/dist/{chunk-YMUSHPU4.js → chunk-WHPBNUKB.js} +35 -1
  16. package/dist/{chunk-RZM5CNIY.js → chunk-XKQVUFUS.js} +72 -15
  17. package/dist/{chunk-RRBXV7KE.js → chunk-XQ4HRPDJ.js} +384 -102
  18. package/dist/cli.js +79 -29
  19. package/dist/{daemon-client-GKEPT4NY.js → daemon-client-MMDRYCF5.js} +35 -1
  20. package/dist/{daemon-client-O6BYVRXV.js → daemon-client-Y2YSZCGK.js} +1 -1
  21. package/dist/daemon-main.js +103 -32
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.js +94 -31
  24. package/dist/{session-recorder-H3KEYU26.js → session-recorder-3BEVWHOK.js} +1 -1
  25. package/dist/{session-recorder-QRZMKFVL.js → session-recorder-SLDBENVF.js} +1 -1
  26. package/dist/{session-replayer-LJUC4TI7.js → session-replayer-WIUYVN5J.js} +90 -2
  27. package/package.json +1 -1
@@ -1,3 +1,7 @@
1
+ import {
2
+ queryAllJS,
3
+ queryJS
4
+ } from "./chunk-3FWLW7FS.js";
1
5
  import {
2
6
  connectToCDP,
3
7
  launchChrome
@@ -18,6 +22,161 @@ import { EventEmitter as EventEmitter2 } from "events";
18
22
  // src/cdp-driver/page.ts
19
23
  import { EventEmitter } from "events";
20
24
 
25
+ // src/cdp-driver/stealth.ts
26
+ var DEFAULT_STEALTH_CONFIG = {
27
+ bezierCurvature: [0.35, 0.6],
28
+ noiseAmplitude: 5.5,
29
+ overshootRange: [6, 14],
30
+ aimPause: [150, 400],
31
+ pressDuration: [60, 140],
32
+ releaseDrift: [0.8, 2.5],
33
+ landingOffsetSmall: [0.3, 2.5],
34
+ landingOffsetLarge: [1.5, 7],
35
+ smallElementThreshold: 30,
36
+ typingRhythm: {
37
+ fastProb: 0.22,
38
+ fastRange: [25, 60],
39
+ normalRange: [50, 350],
40
+ pauseProb: 0.18,
41
+ pauseRange: [400, 1200]
42
+ },
43
+ keyPressDuration: [50, 110],
44
+ typoProbability: 0.06,
45
+ wheelPeak: 180,
46
+ wheelDecayRate: 0.4
47
+ };
48
+ function rand(min, max) {
49
+ return min + Math.random() * (max - min);
50
+ }
51
+ function cosineEase(t) {
52
+ return 0.5 - 0.5 * Math.cos(Math.PI * t);
53
+ }
54
+ function bezierTrajectory(x0, y0, x1, y1, config = DEFAULT_STEALTH_CONFIG) {
55
+ const dist = Math.hypot(x1 - x0, y1 - y0);
56
+ const n = Math.max(10, Math.min(28, Math.round(dist / 15)));
57
+ const shortMove = dist < 120;
58
+ const curvature = shortMove ? rand(2, 6) : Math.max(dist * rand(...config.bezierCurvature), rand(18, 35));
59
+ const dir = Math.random() < 0.5 ? 1 : -1;
60
+ const d = dist || 1;
61
+ const dx = x1 - x0, dy = y1 - y0;
62
+ const c1x = x0 + dx * 0.3 - dy / d * curvature * 0.5 * dir;
63
+ const c1y = y0 + dy * 0.3 + dx / d * curvature * 0.5 * dir;
64
+ const c2x = x0 + dx * 0.7 - dy / d * curvature * 0.8 * dir;
65
+ const c2y = y0 + dy * 0.7 + dx / d * curvature * 0.8 * dir;
66
+ const points = [];
67
+ for (let i = 1; i <= n; i++) {
68
+ const t = cosineEase(i / n);
69
+ const mt = 1 - t;
70
+ let px = mt ** 3 * x0 + 3 * mt ** 2 * t * c1x + 3 * mt * t ** 2 * c2x + t ** 3 * x1;
71
+ let py = mt ** 3 * y0 + 3 * mt ** 2 * t * c1y + 3 * mt * t ** 2 * c2y + t ** 3 * y1;
72
+ const amp = shortMove ? Math.min(2, config.noiseAmplitude) : config.noiseAmplitude;
73
+ px += rand(-amp, amp);
74
+ py += rand(-amp, amp);
75
+ points.push({ x: px, y: py, delay: rand(9, 16) });
76
+ }
77
+ if (!shortMove) {
78
+ const over = rand(...config.overshootRange);
79
+ const ox = x1 + dx / d * over + rand(-2, 2);
80
+ const oy = y1 + dy / d * over + rand(-2, 2);
81
+ points.push({ x: ox, y: oy, delay: rand(14, 30) });
82
+ points.push({
83
+ x: x1 + dx / d * over * 0.4,
84
+ y: y1 + dy / d * over * 0.4,
85
+ delay: rand(14, 30)
86
+ });
87
+ }
88
+ points.push({ x: x1 + rand(-1, 1), y: y1 + rand(-1, 1), delay: rand(14, 30) });
89
+ return points;
90
+ }
91
+ function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
92
+ const isSmall = Math.min(width, height) < config.smallElementThreshold;
93
+ const range = isSmall ? config.landingOffsetSmall : config.landingOffsetLarge;
94
+ const dx = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
95
+ const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
96
+ return { dx, dy };
97
+ }
98
+ var KEY_MAP = {};
99
+ for (let i = 97; i <= 122; i++) {
100
+ const ch = String.fromCharCode(i);
101
+ KEY_MAP[ch] = { key: ch, code: "Key" + ch.toUpperCase(), vk: i - 32 };
102
+ }
103
+ for (let i = 65; i <= 90; i++) {
104
+ const ch = String.fromCharCode(i);
105
+ KEY_MAP[ch] = { key: ch, code: "Key" + ch, vk: i, shift: true };
106
+ }
107
+ for (let i = 48; i <= 57; i++) {
108
+ const ch = String.fromCharCode(i);
109
+ KEY_MAP[ch] = { key: ch, code: "Digit" + ch, vk: i };
110
+ }
111
+ Object.assign(KEY_MAP, {
112
+ " ": { key: " ", code: "Space", vk: 32 },
113
+ ".": { key: ".", code: "Period", vk: 190 },
114
+ "-": { key: "-", code: "Minus", vk: 189 },
115
+ "@": { key: "@", code: "Digit2", vk: 50, shift: true },
116
+ "_": { key: "_", code: "Minus", vk: 189, shift: true }
117
+ });
118
+ function buildStealthInitScript() {
119
+ return [
120
+ "(function(){",
121
+ // 1. AEL event proxy
122
+ " var o=EventTarget.prototype.addEventListener;",
123
+ " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
124
+ " var _ael=function(t,f){",
125
+ " var op=arguments[2];",
126
+ ' if(typeof f!=="function")return o.call(this,t,f,op);',
127
+ " var w=function(e){",
128
+ " if(!e||e.constructor===FocusEvent||e.constructor===KeyboardEvent)return f.call(this,e);",
129
+ " return f.call(this,new Proxy(e,{get:function(k,p){",
130
+ ' if(p==="sourceCapabilities")return fc;',
131
+ ' if(p==="isTrusted")return true;',
132
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isFinite(k[p])){',
133
+ " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
134
+ " return k[p]+_f;",
135
+ " }",
136
+ ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
137
+ " }}));",
138
+ " };",
139
+ " return o.call(this,t,w,op);",
140
+ " };",
141
+ " EventTarget.prototype.addEventListener=_ael;",
142
+ // 2. Screen override (prototype-level, not instance-level)
143
+ " var _gw=function(){return 1728};",
144
+ " var _gh=function(){return 1117};",
145
+ " var _gah=function(){return 1092};",
146
+ ' Object.defineProperty(Screen.prototype,"width",{get:_gw,configurable:true});',
147
+ ' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
148
+ ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
149
+ ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
150
+ " document.hasFocus=function(){return true};",
151
+ // 3. toString disguise (name-list based)
152
+ " var _ts=Function.prototype.toString;",
153
+ " var _hf=document.hasFocus;",
154
+ " Function.prototype.toString=function(){",
155
+ ' if(this===_ael)return"function addEventListener(type, callback) { [native code] }";',
156
+ ' if(this===_hf)return"function hasFocus() { [native code] }";',
157
+ ' if(this===_gw)return"function get width() { [native code] }";',
158
+ ' if(this===_gh)return"function get height() { [native code] }";',
159
+ ' if(this===_gah)return"function get availHeight() { [native code] }";',
160
+ " return _ts.call(this);",
161
+ " };",
162
+ // 4. onclick prototype hijack (dual-stream consistency)
163
+ " var _ba=function(k,p){",
164
+ ' if(p==="isTrusted")return true;',
165
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isFinite(k[p])){',
166
+ " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
167
+ " }",
168
+ ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
169
+ " };",
170
+ ' Object.defineProperty(Document.prototype,"onclick",{',
171
+ " configurable:true,",
172
+ " get:function(){var raw=this.__ocRaw||null;if(!raw)return null;var self=this;",
173
+ " return function(e){return raw.call(self,new Proxy(e,{get:function(k,p){return _ba(k,p)}}))}},",
174
+ " set:function(fn){this.__ocRaw=fn}",
175
+ " });",
176
+ "})()"
177
+ ].join("\n");
178
+ }
179
+
21
180
  // src/cdp-driver/mouse.ts
22
181
  var XBMouseImpl = class {
23
182
  conn;
@@ -39,18 +198,35 @@ var XBMouseImpl = class {
39
198
  }
40
199
  async click(x, y, opts = {}) {
41
200
  const button = opts.button ?? "left";
42
- const clickCount = opts.clickCount ?? 1;
43
- const delay = opts.delay ?? 0;
44
- await this.move(x, y);
45
- await this.down({ button });
46
- if (delay > 0) {
47
- await sleep(delay);
201
+ const stealth = opts.stealth ?? true;
202
+ let tx = x, ty = y;
203
+ if (stealth && opts.elementWidth !== void 0 && opts.elementHeight !== void 0) {
204
+ const off = landingOffset(opts.elementWidth, opts.elementHeight);
205
+ tx += off.dx;
206
+ ty += off.dy;
207
+ }
208
+ if (stealth) {
209
+ const traj = bezierTrajectory(this._x, this._y, tx, ty);
210
+ for (const p of traj) {
211
+ await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: this._button });
212
+ this._x = p.x;
213
+ this._y = p.y;
214
+ await sleep(p.delay);
215
+ }
216
+ await sleep(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
217
+ } else {
218
+ await this.move(tx, ty);
48
219
  }
49
- await this.up({ button });
50
- for (let i = 1; i < clickCount; i++) {
51
- if (delay > 0) await sleep(delay);
220
+ await this.down({ button });
221
+ await sleep(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
222
+ const rx = stealth ? this._x + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._x;
223
+ const ry = stealth ? this._y + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._y;
224
+ this._x = rx;
225
+ this._y = ry;
226
+ await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: rx, y: ry, button, clickCount: opts.clickCount ?? 1 });
227
+ for (let i = 1; i < (opts.clickCount ?? 1); i++) {
228
+ if (opts.delay) await sleep(opts.delay);
52
229
  await this.down({ button });
53
- if (delay > 0) await sleep(delay);
54
230
  await this.up({ button });
55
231
  }
56
232
  }
@@ -229,7 +405,7 @@ var XBKeyboardImpl = class {
229
405
  }
230
406
  };
231
407
  function resolveKeyMapping(key) {
232
- if (KEY_MAP[key]) return KEY_MAP[key];
408
+ if (KEY_MAP2[key]) return KEY_MAP2[key];
233
409
  if (key.length === 1) {
234
410
  const lower = key.toLowerCase();
235
411
  if (lower >= "a" && lower <= "z") {
@@ -246,7 +422,7 @@ function resolveKeyMapping(key) {
246
422
  }
247
423
  return { key, code: key };
248
424
  }
249
- var KEY_MAP = {
425
+ var KEY_MAP2 = {
250
426
  Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
251
427
  Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
252
428
  Escape: { key: "Escape", code: "Escape", keyCode: 27 },
@@ -282,96 +458,57 @@ function sleep2(ms) {
282
458
  return new Promise((resolve) => setTimeout(resolve, ms));
283
459
  }
284
460
 
285
- // src/cdp-driver/selector-utils.ts
286
- function queryJS(selector) {
287
- if (selector.startsWith("xpath=")) {
288
- const xpath = JSON.stringify(selector.slice(6));
289
- return `document.evaluate(${xpath}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue`;
290
- }
291
- if (selector.startsWith("text=")) {
292
- const raw = selector.slice(5);
293
- const exact = raw.startsWith('"') && raw.endsWith('"');
294
- const text = exact ? raw.slice(1, -1) : raw;
295
- return `(() => {
296
- const target = ${JSON.stringify(text)};
297
- const exact = ${exact};
298
- const els = [...document.querySelectorAll('*')].filter(e => {
299
- if (e.children.length > 0) return false;
300
- if (e.offsetParent === null) return false;
301
- const t = (e.textContent || '').trim();
302
- if (!t) return false;
303
- return exact ? t === target : t.toLowerCase().includes(target.toLowerCase());
304
- });
305
- return els[0] || null;
306
- })()`;
307
- }
308
- if (selector.startsWith("popup-text=")) {
309
- const text = selector.slice("popup-text=".length);
310
- return `(() => {
311
- const target = ${JSON.stringify(text)};
312
- const els = [...document.querySelectorAll('*')].filter(e => {
313
- if (e.children.length > 0) return false;
314
- if (e.offsetParent === null) return false;
315
- if ((e.textContent || '').trim() !== target) return false;
316
- return true;
317
- });
318
- return els[0] || null;
319
- })()`;
320
- }
321
- return `document.querySelector(${JSON.stringify(selector)})`;
322
- }
323
- function queryAllJS(selector) {
324
- if (selector.startsWith("xpath=")) {
325
- const xpath = JSON.stringify(selector.slice(6));
326
- return `(() => { const it = document.evaluate(${xpath}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const r=[]; for(let i=0;i<it.snapshotLength;i++) r.push(it.snapshotItem(i)); return r; })()`;
327
- }
328
- if (selector.startsWith("text=") || selector.startsWith("popup-text=")) {
329
- return `(() => { const el = ${queryJS(selector)}; return el ? [el] : []; })()`;
330
- }
331
- return `document.querySelectorAll(${JSON.stringify(selector)})`;
332
- }
333
-
334
461
  // src/cdp-driver/actionability.ts
335
462
  async function waitForActionable(page, selector, opts = {}) {
336
463
  const timeout = opts.timeout ?? 3e4;
337
464
  if (opts.force) {
338
- if (selector.startsWith("xpath=")) {
339
- const rect2 = await page.evaluate(`
465
+ const deadline2 = Date.now() + timeout;
466
+ let lastError;
467
+ while (Date.now() < deadline2) {
468
+ const rect = await page.evaluate(`
340
469
  (function() {
341
470
  const el = ${queryJS(selector)};
342
471
  if (!el) return null;
343
472
  const r = el.getBoundingClientRect();
344
- return { x: r.x, y: r.y, width: r.width, height: r.height };
473
+ let x = r.x, y = r.y;
474
+ let doc = el.ownerDocument;
475
+ while (doc !== document) {
476
+ let host = null;
477
+ const scan = (d) => {
478
+ let frames;
479
+ try { frames = d.querySelectorAll('iframe'); } catch (e) { return null; }
480
+ for (const f of frames) {
481
+ let inner = null;
482
+ try { inner = f.contentDocument; } catch (e) { continue; }
483
+ if (!inner) continue;
484
+ if (inner === doc) return f;
485
+ const rr = scan(inner);
486
+ if (rr) return rr;
487
+ }
488
+ return null;
489
+ };
490
+ host = scan(document);
491
+ if (!host) break;
492
+ const hr = host.getBoundingClientRect();
493
+ x += hr.x; y += hr.y;
494
+ doc = host.ownerDocument;
495
+ }
496
+ return { x, y, width: r.width, height: r.height };
345
497
  })()
346
- `);
347
- if (!rect2) throw new Error(`Element not found: ${selector}`);
348
- return { nodeId: 0, rect: rect2 };
498
+ `).catch(() => null);
499
+ if (rect && rect.width > 0 && rect.height > 0) return { nodeId: 0, rect };
500
+ lastError = `Element not visible (zero size): ${selector}`;
501
+ lastError = `Element not found: ${selector}`;
502
+ await page.waitForTimeout(200);
349
503
  }
350
- const deadline2 = Date.now() + timeout;
351
- let lastError;
352
- let nodeId = 0;
353
- let rect = null;
354
- while (Date.now() < deadline2) {
355
- nodeId = await page.querySelector(selector);
356
- if (!nodeId) {
357
- lastError = `Element not found: ${selector}`;
358
- await page.waitForTimeout(200);
359
- continue;
360
- }
361
- rect = await page.getBoxModel(nodeId);
362
- if (rect) break;
363
- lastError = `Element has no box: ${selector}`;
364
- await page.waitForTimeout(500);
365
- }
366
- if (!rect) throw new Error(lastError || `Element has no box: ${selector}`);
367
- return { nodeId, rect };
504
+ throw new Error(lastError || `Element not found: ${selector}`);
368
505
  }
369
506
  const deadline = Date.now() + timeout;
370
507
  while (Date.now() < deadline) {
371
508
  const result = await checkActionable(page, selector);
372
509
  if (result.ok && result.rect) {
373
- const nodeId = await page.querySelector(selector);
374
- if (nodeId) return { nodeId, rect: result.rect };
510
+ const nodeId = await page.querySelector(selector).catch(() => 0) ?? 0;
511
+ return { nodeId, rect: result.rect };
375
512
  }
376
513
  await page.waitForTimeout(50);
377
514
  }
@@ -404,12 +541,25 @@ async function checkActionable(page, selector) {
404
541
  return { ok: false, reason: 'parent_disabled' };
405
542
  }
406
543
 
407
- // Check not covered by another element at center
544
+ // Check not covered by another element at center.
545
+ // elementFromPoint must run in the element's OWN document: for iframe-
546
+ // internal elements the main-document hit-test returns the <iframe>
547
+ // host itself, which falsely reports "covered" (rec-duel d01).
548
+ // For shadow-internal elements the hit-test retargets to the shadow
549
+ // HOST \u2014 walk the host chain before declaring coverage (rec-duel d04).
408
550
  const cx = rect.x + rect.width / 2;
409
551
  const cy = rect.y + rect.height / 2;
410
- const topEl = document.elementFromPoint(cx, cy);
552
+ const topEl = el.ownerDocument.elementFromPoint(cx, cy);
411
553
  if (topEl && topEl !== el && !el.contains(topEl) && !topEl.contains(el)) {
412
- return { ok: false, reason: 'covered', rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
554
+ let hostChain = [];
555
+ let rootNode = el.getRootNode();
556
+ while (rootNode && rootNode.host) {
557
+ hostChain.push(rootNode.host);
558
+ rootNode = rootNode.host.getRootNode();
559
+ }
560
+ if (!hostChain.includes(topEl)) {
561
+ return { ok: false, reason: 'covered', rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
562
+ }
413
563
  }
414
564
 
415
565
  return {
@@ -454,21 +604,48 @@ var XBLocatorImpl = class _XBLocatorImpl {
454
604
  const el = ${this._q(this.selector)};
455
605
  if (!el) return null;
456
606
  const rect = el.getBoundingClientRect();
457
- return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
607
+ let x = rect.x, y = rect.y;
608
+ let doc = el.ownerDocument;
609
+ while (doc !== document) {
610
+ let host = null;
611
+ const scan = (d) => {
612
+ let frames;
613
+ try { frames = d.querySelectorAll('iframe'); } catch (e) { return null; }
614
+ for (const f of frames) {
615
+ let inner = null;
616
+ try { inner = f.contentDocument; } catch (e) { continue; }
617
+ if (!inner) continue;
618
+ if (inner === doc) return f;
619
+ const r = scan(inner);
620
+ if (r) return r;
621
+ }
622
+ return null;
623
+ };
624
+ host = scan(document);
625
+ if (!host) break;
626
+ const hr = host.getBoundingClientRect();
627
+ x += hr.x; y += hr.y;
628
+ doc = host.ownerDocument;
629
+ }
630
+ return { x, y, width: rect.width, height: rect.height };
458
631
  })()
459
632
  `);
460
633
  const finalRect = updatedRect ?? rect;
461
634
  const cx = finalRect.x + finalRect.width / 2;
462
635
  const cy = finalRect.y + finalRect.height / 2;
463
636
  await this.page.mouse.click(cx, cy, {
464
- button: opts.button ?? "left",
465
- clickCount: opts.clickCount ?? 1,
466
- delay: opts.delay
637
+ stealth: true,
638
+ elementWidth: finalRect.width,
639
+ elementHeight: finalRect.height,
640
+ ...{ button: opts.button ?? "left", clickCount: opts.clickCount ?? 1, delay: opts.delay }
467
641
  });
468
642
  }
469
643
  async fill(value, opts = {}) {
470
644
  await waitForActionable(this.page, this.selector, opts);
471
645
  await scrollIntoView(this.page, this.selector);
646
+ await this.click({ ...opts });
647
+ await this.page.keyboard.type(value, { stealth: true });
648
+ return;
472
649
  await this.page.evaluate(`
473
650
  (function() {
474
651
  const el = ${this._q(this.selector)};
@@ -1178,6 +1355,8 @@ var XBPageImpl = class _XBPageImpl {
1178
1355
  await this.conn.send("Page.enable", void 0, this.sessionId);
1179
1356
  await this.conn.send("Runtime.enable", void 0, this.sessionId);
1180
1357
  await this.conn.send("Network.enable", void 0, this.sessionId);
1358
+ await this.conn.send("DOM.enable", void 0, this.sessionId).catch(() => {
1359
+ });
1181
1360
  this.setupPageEvents();
1182
1361
  this.setupNetworkEvents();
1183
1362
  this.setupConsoleEvents();
@@ -1190,6 +1369,9 @@ var XBPageImpl = class _XBPageImpl {
1190
1369
  );
1191
1370
  this._url = info.url;
1192
1371
  this._title = info.title;
1372
+ if (info.url && info.url !== "about:blank" && info.url !== "") {
1373
+ this._loadState = { loadFired: true, domContentFired: true, networkIdle: true };
1374
+ }
1193
1375
  } catch {
1194
1376
  }
1195
1377
  }
@@ -1202,6 +1384,16 @@ var XBPageImpl = class _XBPageImpl {
1202
1384
  const waitUntil = opts.waitUntil ?? "load";
1203
1385
  const timeout = opts.timeout ?? 3e4;
1204
1386
  this._loadState = { loadFired: false, domContentFired: false, networkIdle: false };
1387
+ if (process.env.XBROWSER_STEALTH !== "off") {
1388
+ try {
1389
+ await this.conn.send(
1390
+ "Page.addScriptToEvaluateOnNewDocument",
1391
+ { source: buildStealthInitScript() },
1392
+ this.sessionId
1393
+ );
1394
+ } catch {
1395
+ }
1396
+ }
1205
1397
  const result = await this.conn.send(
1206
1398
  "Page.navigate",
1207
1399
  { url, referrer: opts.referer },
@@ -1409,6 +1601,91 @@ Last error: ${lastError.message}` : "";
1409
1601
  }
1410
1602
  return result.result?.value;
1411
1603
  }
1604
+ /**
1605
+ * 在指定 iframe 上下文中执行表达式(攻防 D16 能力建设,2026-08-19)。
1606
+ *
1607
+ * 双路径:
1608
+ * 1. 同进程 iframe —— Runtime.enable 收集 executionContextCreated,
1609
+ * 找到目标 frameId 的 contextId,用 contextId 定向执行;
1610
+ * 2. 跨域 OOPIF(独立 target)—— Target.setAutoAttach(flatten) 监听
1611
+ * attachedToTarget 中 type==='iframe' 的会话,用其 sessionId 执行。
1612
+ *
1613
+ * 这绕过了页面同源策略(那是页面 JS 的约束,CDP 是调试通道)——
1614
+ * 支付窗/验证码/第三方嵌入内容的读写都靠它。
1615
+ */
1616
+ async evaluateInFrame(urlIncludes, expression) {
1617
+ if (this._closed) throw new Error("Page is closed");
1618
+ const evalIn = async (sessionId, contextId) => {
1619
+ const params = { expression, returnByValue: true, awaitPromise: true };
1620
+ if (contextId !== void 0) params.contextId = contextId;
1621
+ const result = await this.conn.send("Runtime.evaluate", params, sessionId);
1622
+ if (result.exceptionDetails) {
1623
+ const detail = result.exceptionDetails.exception?.description ?? result.exceptionDetails.exception?.value ?? result.exceptionDetails.text;
1624
+ throw new Error(`[frame ${urlIncludes}] ${detail}`);
1625
+ }
1626
+ return result.result?.value;
1627
+ };
1628
+ try {
1629
+ const tg = await this.conn.send("Target.getTargets", void 0);
1630
+ const hit2 = (tg.targetInfos || []).find((t) => t.type === "iframe" && (t.url || "").includes(urlIncludes));
1631
+ if (hit2) {
1632
+ const att = await this.conn.send("Target.attachToTarget", { targetId: hit2.targetId, flatten: true });
1633
+ return evalIn(att.sessionId);
1634
+ }
1635
+ } catch {
1636
+ }
1637
+ const tree = await this.conn.send("Page.getFrameTree", void 0, this.sessionId);
1638
+ const all = [];
1639
+ const walk = (node) => {
1640
+ all.push({ id: node.frame.id, url: node.frame.url });
1641
+ for (const child of node.childFrames || []) walk(child);
1642
+ };
1643
+ walk(tree.frameTree);
1644
+ const mainId = tree.frameTree?.frame?.id;
1645
+ const target = all.find((f) => f.id !== mainId && f.url.includes(urlIncludes));
1646
+ if (target) {
1647
+ const contexts = [];
1648
+ const onCtx = (raw) => {
1649
+ const c = raw?.context;
1650
+ if (c?.id && c?.auxData?.frameId) contexts.push({ id: c.id, frameId: c.auxData.frameId });
1651
+ };
1652
+ this.conn.on("Runtime.executionContextCreated", onCtx);
1653
+ try {
1654
+ await this.conn.send("Runtime.enable", void 0, this.sessionId).catch(() => {
1655
+ });
1656
+ await new Promise((r) => setTimeout(r, 400));
1657
+ } finally {
1658
+ this.conn.off("Runtime.executionContextCreated", onCtx);
1659
+ }
1660
+ const ctx = contexts.find((c) => c.frameId === target.id);
1661
+ if (ctx) return evalIn(this.sessionId, ctx.id);
1662
+ }
1663
+ const attached = [];
1664
+ const onAttach = (raw) => {
1665
+ const ev = raw;
1666
+ if (ev?.sessionId && ev.targetInfo?.type === "iframe") {
1667
+ attached.push({ sessionId: ev.sessionId, url: ev.targetInfo.url || "" });
1668
+ }
1669
+ };
1670
+ this.conn.on("Target.attachedToTarget", onAttach);
1671
+ try {
1672
+ await this.conn.send("Target.setAutoAttach", {
1673
+ autoAttach: true,
1674
+ waitForDebuggerOnStart: false,
1675
+ flatten: true
1676
+ }, this.sessionId);
1677
+ await new Promise((r) => setTimeout(r, 600));
1678
+ } finally {
1679
+ this.conn.off("Target.attachedToTarget", onAttach);
1680
+ this.conn.send("Target.setAutoAttach", { autoAttach: false, waitForDebuggerOnStart: false, flatten: true }, this.sessionId).catch(() => {
1681
+ });
1682
+ }
1683
+ const hit = attached.find((a) => a.url.includes(urlIncludes));
1684
+ if (!hit) {
1685
+ throw new Error(`frame not found for "${urlIncludes}"\uFF08OOPIF target\u3001frame \u6811\u3001auto-attach \u4E09\u8DEF\u5747\u672A\u547D\u4E2D\uFF1B\u53EF\u80FD\u4ECD\u5728\u52A0\u8F7D\uFF09`);
1686
+ }
1687
+ return evalIn(hit.sessionId);
1688
+ }
1412
1689
  /** evaluateHandle — evaluates fn and returns a handle for element bounding box */
1413
1690
  async evaluateHandle(fn, ...args) {
1414
1691
  let expression;
@@ -1779,16 +2056,21 @@ Last error: ${lastError.message}` : "";
1779
2056
  }
1780
2057
  return 1;
1781
2058
  }
1782
- const doc = await this.conn.send(
1783
- "DOM.getDocument",
1784
- { depth: 0 },
1785
- this.sessionId
2059
+ const withTimeout = (p, ms) => Promise.race([p, new Promise((r) => setTimeout(() => r(null), ms))]);
2060
+ const doc = await withTimeout(
2061
+ this.conn.send("DOM.getDocument", { depth: 0 }, this.sessionId),
2062
+ 8e3
1786
2063
  );
1787
- const result = await this.conn.send(
1788
- "DOM.querySelector",
1789
- { nodeId: doc.root.nodeId, selector },
1790
- this.sessionId
2064
+ if (!doc) return 0;
2065
+ const result = await withTimeout(
2066
+ this.conn.send(
2067
+ "DOM.querySelector",
2068
+ { nodeId: doc.root.nodeId, selector },
2069
+ this.sessionId
2070
+ ),
2071
+ 8e3
1791
2072
  );
2073
+ if (!result) return 0;
1792
2074
  return result.nodeId;
1793
2075
  }
1794
2076
  /** Query all matching elements, returns array of CDP nodeIds */