@btraut/browser-bridge 0.7.3 → 0.8.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.
- package/CHANGELOG.md +9 -1
- package/README.md +8 -0
- package/extension/dist/background.js +725 -8
- package/extension/dist/background.js.map +3 -3
- package/extension/dist/content.js +78 -0
- package/extension/dist/content.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/browser-bridge/skill.json +1 -1
|
@@ -1445,14 +1445,447 @@ var DriveSocket = class {
|
|
|
1445
1445
|
}
|
|
1446
1446
|
return;
|
|
1447
1447
|
}
|
|
1448
|
-
case "drive.click":
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1448
|
+
case "drive.click": {
|
|
1449
|
+
const params = message.params ?? {};
|
|
1450
|
+
let tabId = params.tab_id;
|
|
1451
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1452
|
+
respondError({
|
|
1453
|
+
code: "INVALID_ARGUMENT",
|
|
1454
|
+
message: "tab_id must be a number when provided.",
|
|
1455
|
+
retryable: false
|
|
1456
|
+
});
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
if (tabId === void 0) {
|
|
1460
|
+
tabId = await getDefaultTabId();
|
|
1461
|
+
}
|
|
1462
|
+
const clickCount = params.click_count;
|
|
1463
|
+
const count = typeof clickCount === "number" && Number.isFinite(clickCount) ? Math.max(1, Math.floor(clickCount)) : 1;
|
|
1464
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1465
|
+
if (error) {
|
|
1466
|
+
respondError(error);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const pointResult = await this.resolveLocatorPoint(
|
|
1470
|
+
tabId,
|
|
1471
|
+
params.locator
|
|
1472
|
+
);
|
|
1473
|
+
if (!pointResult.ok) {
|
|
1474
|
+
respondError(pointResult.error);
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
const { x, y } = pointResult.point;
|
|
1478
|
+
self.setTimeout(() => {
|
|
1479
|
+
void this.dispatchCdpClick(tabId, x, y, count).catch(
|
|
1480
|
+
(error2) => {
|
|
1481
|
+
console.debug("Deferred CDP click failed.", error2);
|
|
1482
|
+
}
|
|
1483
|
+
);
|
|
1484
|
+
}, 0);
|
|
1485
|
+
respondOk({ ok: true });
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
case "drive.hover": {
|
|
1489
|
+
const params = message.params ?? {};
|
|
1490
|
+
let tabId = params.tab_id;
|
|
1491
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1492
|
+
respondError({
|
|
1493
|
+
code: "INVALID_ARGUMENT",
|
|
1494
|
+
message: "tab_id must be a number when provided.",
|
|
1495
|
+
retryable: false
|
|
1496
|
+
});
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
if (tabId === void 0) {
|
|
1500
|
+
tabId = await getDefaultTabId();
|
|
1501
|
+
}
|
|
1502
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1503
|
+
if (error) {
|
|
1504
|
+
respondError(error);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const pointResult = await this.resolveLocatorPoint(
|
|
1508
|
+
tabId,
|
|
1509
|
+
params.locator
|
|
1510
|
+
);
|
|
1511
|
+
if (!pointResult.ok) {
|
|
1512
|
+
respondError(pointResult.error);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
const { x, y } = pointResult.point;
|
|
1516
|
+
const waitMs = typeof params.delay_ms === "number" && Number.isFinite(params.delay_ms) ? Math.min(Math.max(params.delay_ms, 0), 1e4) : 0;
|
|
1517
|
+
try {
|
|
1518
|
+
await this.dispatchCdpMouseMove(tabId, x, y, 0);
|
|
1519
|
+
if (waitMs > 0) {
|
|
1520
|
+
await delayMs(waitMs);
|
|
1521
|
+
}
|
|
1522
|
+
const snapshot = await sendToTab(
|
|
1523
|
+
tabId,
|
|
1524
|
+
"drive.snapshot_html"
|
|
1525
|
+
);
|
|
1526
|
+
if (!snapshot.ok) {
|
|
1527
|
+
respondError(snapshot.error);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
respondOk(snapshot.result ?? { format: "html", snapshot: "" });
|
|
1531
|
+
} catch (error2) {
|
|
1532
|
+
const info = mapDebuggerErrorMessage(
|
|
1533
|
+
error2 instanceof Error ? error2.message : "Hover dispatch failed."
|
|
1534
|
+
);
|
|
1535
|
+
respondError(info);
|
|
1536
|
+
}
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
case "drive.drag": {
|
|
1540
|
+
const params = message.params ?? {};
|
|
1541
|
+
let tabId = params.tab_id;
|
|
1542
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1543
|
+
respondError({
|
|
1544
|
+
code: "INVALID_ARGUMENT",
|
|
1545
|
+
message: "tab_id must be a number when provided.",
|
|
1546
|
+
retryable: false
|
|
1547
|
+
});
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
if (tabId === void 0) {
|
|
1551
|
+
tabId = await getDefaultTabId();
|
|
1552
|
+
}
|
|
1553
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1554
|
+
if (error) {
|
|
1555
|
+
respondError(error);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
const fromResult = await this.resolveLocatorPoint(
|
|
1559
|
+
tabId,
|
|
1560
|
+
params.from
|
|
1561
|
+
);
|
|
1562
|
+
if (!fromResult.ok) {
|
|
1563
|
+
respondError(fromResult.error);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
const toResult = await this.resolveLocatorPoint(
|
|
1567
|
+
tabId,
|
|
1568
|
+
params.to
|
|
1569
|
+
);
|
|
1570
|
+
if (!toResult.ok) {
|
|
1571
|
+
respondError(toResult.error);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
const steps = typeof params.steps === "number" && Number.isFinite(params.steps) ? Math.max(1, Math.min(50, Math.floor(params.steps))) : 12;
|
|
1575
|
+
try {
|
|
1576
|
+
await this.dispatchCdpDrag(
|
|
1577
|
+
tabId,
|
|
1578
|
+
fromResult.point,
|
|
1579
|
+
toResult.point,
|
|
1580
|
+
steps
|
|
1581
|
+
);
|
|
1582
|
+
respondOk({ ok: true });
|
|
1583
|
+
} catch (error2) {
|
|
1584
|
+
const info = mapDebuggerErrorMessage(
|
|
1585
|
+
error2 instanceof Error ? error2.message : "Drag dispatch failed."
|
|
1586
|
+
);
|
|
1587
|
+
respondError(info);
|
|
1588
|
+
}
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
case "drive.key_press": {
|
|
1592
|
+
const params = message.params ?? {};
|
|
1593
|
+
const key = params.key;
|
|
1594
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
1595
|
+
respondError({
|
|
1596
|
+
code: "INVALID_ARGUMENT",
|
|
1597
|
+
message: "key must be a non-empty string.",
|
|
1598
|
+
retryable: false
|
|
1599
|
+
});
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
let tabId = params.tab_id;
|
|
1603
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1604
|
+
respondError({
|
|
1605
|
+
code: "INVALID_ARGUMENT",
|
|
1606
|
+
message: "tab_id must be a number when provided.",
|
|
1607
|
+
retryable: false
|
|
1608
|
+
});
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (tabId === void 0) {
|
|
1612
|
+
tabId = await getDefaultTabId();
|
|
1613
|
+
}
|
|
1614
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1615
|
+
if (error) {
|
|
1616
|
+
respondError(error);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
try {
|
|
1620
|
+
await this.dispatchCdpKeyPress(
|
|
1621
|
+
tabId,
|
|
1622
|
+
key,
|
|
1623
|
+
params.modifiers
|
|
1624
|
+
);
|
|
1625
|
+
respondOk({ ok: true });
|
|
1626
|
+
} catch (error2) {
|
|
1627
|
+
const info = mapDebuggerErrorMessage(
|
|
1628
|
+
error2 instanceof Error ? error2.message : "Keyboard dispatch failed."
|
|
1629
|
+
);
|
|
1630
|
+
respondError(info);
|
|
1631
|
+
}
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
case "drive.key": {
|
|
1635
|
+
const params = message.params ?? {};
|
|
1636
|
+
const key = params.key;
|
|
1637
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
1638
|
+
respondError({
|
|
1639
|
+
code: "INVALID_ARGUMENT",
|
|
1640
|
+
message: "key must be a non-empty string.",
|
|
1641
|
+
retryable: false
|
|
1642
|
+
});
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
let tabId = params.tab_id;
|
|
1646
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1647
|
+
respondError({
|
|
1648
|
+
code: "INVALID_ARGUMENT",
|
|
1649
|
+
message: "tab_id must be a number when provided.",
|
|
1650
|
+
retryable: false
|
|
1651
|
+
});
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (tabId === void 0) {
|
|
1655
|
+
tabId = await getDefaultTabId();
|
|
1656
|
+
}
|
|
1657
|
+
const count = typeof params.repeat === "number" && Number.isFinite(params.repeat) ? Math.max(1, Math.min(50, Math.floor(params.repeat))) : 1;
|
|
1658
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1659
|
+
if (error) {
|
|
1660
|
+
respondError(error);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
try {
|
|
1664
|
+
for (let i = 0; i < count; i += 1) {
|
|
1665
|
+
await this.dispatchCdpKeyPress(
|
|
1666
|
+
tabId,
|
|
1667
|
+
key,
|
|
1668
|
+
params.modifiers
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
respondOk({ ok: true });
|
|
1672
|
+
} catch (error2) {
|
|
1673
|
+
const info = mapDebuggerErrorMessage(
|
|
1674
|
+
error2 instanceof Error ? error2.message : "Keyboard dispatch failed."
|
|
1675
|
+
);
|
|
1676
|
+
respondError(info);
|
|
1677
|
+
}
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
case "drive.type": {
|
|
1681
|
+
const params = message.params ?? {};
|
|
1682
|
+
const text = params.text;
|
|
1683
|
+
if (typeof text !== "string") {
|
|
1684
|
+
respondError({
|
|
1685
|
+
code: "INVALID_ARGUMENT",
|
|
1686
|
+
message: "text must be a string.",
|
|
1687
|
+
retryable: false
|
|
1688
|
+
});
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
let tabId = params.tab_id;
|
|
1692
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1693
|
+
respondError({
|
|
1694
|
+
code: "INVALID_ARGUMENT",
|
|
1695
|
+
message: "tab_id must be a number when provided.",
|
|
1696
|
+
retryable: false
|
|
1697
|
+
});
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
if (tabId === void 0) {
|
|
1701
|
+
tabId = await getDefaultTabId();
|
|
1702
|
+
}
|
|
1703
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1704
|
+
if (error) {
|
|
1705
|
+
respondError(error);
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
const result = await this.performCdpType(tabId, {
|
|
1709
|
+
locator: params.locator,
|
|
1710
|
+
text,
|
|
1711
|
+
clear: Boolean(params.clear),
|
|
1712
|
+
submit: Boolean(params.submit)
|
|
1713
|
+
});
|
|
1714
|
+
if (!result.ok) {
|
|
1715
|
+
respondError(result.error);
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
respondOk({ ok: true });
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
case "drive.select": {
|
|
1722
|
+
const params = message.params ?? {};
|
|
1723
|
+
let tabId = params.tab_id;
|
|
1724
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1725
|
+
respondError({
|
|
1726
|
+
code: "INVALID_ARGUMENT",
|
|
1727
|
+
message: "tab_id must be a number when provided.",
|
|
1728
|
+
retryable: false
|
|
1729
|
+
});
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (tabId === void 0) {
|
|
1733
|
+
tabId = await getDefaultTabId();
|
|
1734
|
+
}
|
|
1735
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1736
|
+
if (error) {
|
|
1737
|
+
respondError(error);
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
const pointResult = await this.resolveLocatorPoint(
|
|
1741
|
+
tabId,
|
|
1742
|
+
params.locator
|
|
1743
|
+
);
|
|
1744
|
+
if (!pointResult.ok) {
|
|
1745
|
+
respondError(pointResult.error);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
try {
|
|
1749
|
+
await this.dispatchCdpClick(
|
|
1750
|
+
tabId,
|
|
1751
|
+
pointResult.point.x,
|
|
1752
|
+
pointResult.point.y,
|
|
1753
|
+
1
|
|
1754
|
+
);
|
|
1755
|
+
} catch (error2) {
|
|
1756
|
+
const info = mapDebuggerErrorMessage(
|
|
1757
|
+
error2 instanceof Error ? error2.message : "Select click failed."
|
|
1758
|
+
);
|
|
1759
|
+
respondError(info);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
const selectResult = await sendToTab(
|
|
1763
|
+
tabId,
|
|
1764
|
+
"drive.select",
|
|
1765
|
+
params
|
|
1766
|
+
);
|
|
1767
|
+
if (!selectResult.ok) {
|
|
1768
|
+
respondError(selectResult.error);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
respondOk(selectResult.result ?? { ok: true });
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
case "drive.fill_form": {
|
|
1775
|
+
const params = message.params ?? {};
|
|
1776
|
+
const fields = params.fields;
|
|
1777
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
1778
|
+
respondError({
|
|
1779
|
+
code: "INVALID_ARGUMENT",
|
|
1780
|
+
message: "fields must be a non-empty array.",
|
|
1781
|
+
retryable: false
|
|
1782
|
+
});
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
let tabId = params.tab_id;
|
|
1786
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1787
|
+
respondError({
|
|
1788
|
+
code: "INVALID_ARGUMENT",
|
|
1789
|
+
message: "tab_id must be a number when provided.",
|
|
1790
|
+
retryable: false
|
|
1791
|
+
});
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
if (tabId === void 0) {
|
|
1795
|
+
tabId = await getDefaultTabId();
|
|
1796
|
+
}
|
|
1797
|
+
const error = await this.ensureDebuggerAttached(tabId);
|
|
1798
|
+
if (error) {
|
|
1799
|
+
respondError(error);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
let filled = 0;
|
|
1803
|
+
const errors = [];
|
|
1804
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
1805
|
+
const field = fields[index];
|
|
1806
|
+
if (!field || typeof field !== "object") {
|
|
1807
|
+
errors.push(`Field ${index} is not an object.`);
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
const record = field;
|
|
1811
|
+
const value = record.value;
|
|
1812
|
+
if (typeof value !== "string" && typeof value !== "boolean") {
|
|
1813
|
+
errors.push(`Field ${index} has invalid value.`);
|
|
1814
|
+
continue;
|
|
1815
|
+
}
|
|
1816
|
+
const selector = typeof record.selector === "string" ? record.selector : void 0;
|
|
1817
|
+
const locator = record.locator && typeof record.locator === "object" ? record.locator : selector ? { css: selector } : void 0;
|
|
1818
|
+
let resolvedType = typeof record.type === "string" && record.type.length > 0 ? record.type : "auto";
|
|
1819
|
+
if (resolvedType === "auto") {
|
|
1820
|
+
const detected = await sendToTab(
|
|
1821
|
+
tabId,
|
|
1822
|
+
"drive.detect_field_type",
|
|
1823
|
+
{ locator: record.locator, selector }
|
|
1824
|
+
);
|
|
1825
|
+
if (!detected.ok) {
|
|
1826
|
+
errors.push(`Field ${index} could not be resolved.`);
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
const payload2 = detected.result;
|
|
1830
|
+
if (!payload2 || typeof payload2 !== "object") {
|
|
1831
|
+
errors.push(`Field ${index} returned invalid type payload.`);
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
const detectedType = payload2.fieldType;
|
|
1835
|
+
if (typeof detectedType !== "string" || detectedType.length === 0) {
|
|
1836
|
+
errors.push(`Field ${index} returned invalid field type.`);
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
resolvedType = detectedType;
|
|
1840
|
+
}
|
|
1841
|
+
if ((resolvedType === "text" || resolvedType === "contentEditable") && locator) {
|
|
1842
|
+
const typed = await this.performCdpType(tabId, {
|
|
1843
|
+
locator,
|
|
1844
|
+
text: String(value),
|
|
1845
|
+
clear: true,
|
|
1846
|
+
submit: Boolean(record.submit)
|
|
1847
|
+
});
|
|
1848
|
+
if (!typed.ok) {
|
|
1849
|
+
errors.push(
|
|
1850
|
+
`Field ${index} could not be filled: ${typed.error.message}`
|
|
1851
|
+
);
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
filled += 1;
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
const fallback = await sendToTab(
|
|
1858
|
+
tabId,
|
|
1859
|
+
"drive.fill_form",
|
|
1860
|
+
{
|
|
1861
|
+
fields: [field]
|
|
1862
|
+
}
|
|
1863
|
+
);
|
|
1864
|
+
if (!fallback.ok) {
|
|
1865
|
+
errors.push(
|
|
1866
|
+
`Field ${index} could not be filled: ${fallback.error.message}`
|
|
1867
|
+
);
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
const payload = fallback.result;
|
|
1871
|
+
if (!payload || typeof payload !== "object") {
|
|
1872
|
+
errors.push(`Field ${index} returned invalid fallback payload.`);
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
const fallbackFilled = payload.filled;
|
|
1876
|
+
if (typeof fallbackFilled === "number" && Number.isFinite(fallbackFilled) && fallbackFilled > 0) {
|
|
1877
|
+
filled += 1;
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
errors.push(`Field ${index} could not be filled.`);
|
|
1881
|
+
}
|
|
1882
|
+
respondOk({
|
|
1883
|
+
filled,
|
|
1884
|
+
attempted: fields.length,
|
|
1885
|
+
errors: errors.length > 0 ? errors : []
|
|
1886
|
+
});
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1456
1889
|
case "drive.scroll":
|
|
1457
1890
|
case "drive.wait_for": {
|
|
1458
1891
|
const params = message.params ?? {};
|
|
@@ -1856,6 +2289,290 @@ var DriveSocket = class {
|
|
|
1856
2289
|
});
|
|
1857
2290
|
}
|
|
1858
2291
|
}
|
|
2292
|
+
async dispatchCdpClick(tabId, x, y, clickCount) {
|
|
2293
|
+
await this.dispatchCdpMouseMove(tabId, x, y, 0);
|
|
2294
|
+
for (let i = 0; i < clickCount; i += 1) {
|
|
2295
|
+
const normalizedClickCount = i + 1;
|
|
2296
|
+
await this.sendDebuggerCommand(
|
|
2297
|
+
tabId,
|
|
2298
|
+
"Input.dispatchMouseEvent",
|
|
2299
|
+
{
|
|
2300
|
+
type: "mousePressed",
|
|
2301
|
+
x,
|
|
2302
|
+
y,
|
|
2303
|
+
button: "left",
|
|
2304
|
+
clickCount: normalizedClickCount
|
|
2305
|
+
},
|
|
2306
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2307
|
+
);
|
|
2308
|
+
await this.sendDebuggerCommand(
|
|
2309
|
+
tabId,
|
|
2310
|
+
"Input.dispatchMouseEvent",
|
|
2311
|
+
{
|
|
2312
|
+
type: "mouseReleased",
|
|
2313
|
+
x,
|
|
2314
|
+
y,
|
|
2315
|
+
button: "left",
|
|
2316
|
+
clickCount: normalizedClickCount
|
|
2317
|
+
},
|
|
2318
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
this.touchDebuggerSession(tabId);
|
|
2322
|
+
}
|
|
2323
|
+
async dispatchCdpMouseMove(tabId, x, y, buttons) {
|
|
2324
|
+
await this.sendDebuggerCommand(
|
|
2325
|
+
tabId,
|
|
2326
|
+
"Input.dispatchMouseEvent",
|
|
2327
|
+
{
|
|
2328
|
+
type: "mouseMoved",
|
|
2329
|
+
x,
|
|
2330
|
+
y,
|
|
2331
|
+
button: "none",
|
|
2332
|
+
buttons
|
|
2333
|
+
},
|
|
2334
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
async dispatchCdpDrag(tabId, from, to, steps) {
|
|
2338
|
+
await this.dispatchCdpMouseMove(tabId, from.x, from.y, 0);
|
|
2339
|
+
await this.sendDebuggerCommand(
|
|
2340
|
+
tabId,
|
|
2341
|
+
"Input.dispatchMouseEvent",
|
|
2342
|
+
{
|
|
2343
|
+
type: "mousePressed",
|
|
2344
|
+
x: from.x,
|
|
2345
|
+
y: from.y,
|
|
2346
|
+
button: "left",
|
|
2347
|
+
clickCount: 1
|
|
2348
|
+
},
|
|
2349
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2350
|
+
);
|
|
2351
|
+
for (let i = 1; i <= steps; i += 1) {
|
|
2352
|
+
const progress = i / steps;
|
|
2353
|
+
const x = from.x + (to.x - from.x) * progress;
|
|
2354
|
+
const y = from.y + (to.y - from.y) * progress;
|
|
2355
|
+
await this.dispatchCdpMouseMove(tabId, x, y, 1);
|
|
2356
|
+
await delayMs(10);
|
|
2357
|
+
}
|
|
2358
|
+
await this.sendDebuggerCommand(
|
|
2359
|
+
tabId,
|
|
2360
|
+
"Input.dispatchMouseEvent",
|
|
2361
|
+
{
|
|
2362
|
+
type: "mouseReleased",
|
|
2363
|
+
x: to.x,
|
|
2364
|
+
y: to.y,
|
|
2365
|
+
button: "left",
|
|
2366
|
+
clickCount: 1
|
|
2367
|
+
},
|
|
2368
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2369
|
+
);
|
|
2370
|
+
this.touchDebuggerSession(tabId);
|
|
2371
|
+
}
|
|
2372
|
+
async resolveLocatorPoint(tabId, locator) {
|
|
2373
|
+
const point = await sendToTab(tabId, "drive.locator_point", {
|
|
2374
|
+
locator
|
|
2375
|
+
});
|
|
2376
|
+
if (!point.ok) {
|
|
2377
|
+
return point;
|
|
2378
|
+
}
|
|
2379
|
+
const payload = point.result;
|
|
2380
|
+
if (!payload || typeof payload !== "object") {
|
|
2381
|
+
return {
|
|
2382
|
+
ok: false,
|
|
2383
|
+
error: {
|
|
2384
|
+
code: "EVALUATION_FAILED",
|
|
2385
|
+
message: "Invalid locator point payload.",
|
|
2386
|
+
retryable: false
|
|
2387
|
+
}
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
const record = payload;
|
|
2391
|
+
const x = record.x;
|
|
2392
|
+
const y = record.y;
|
|
2393
|
+
if (typeof x !== "number" || !Number.isFinite(x) || typeof y !== "number" || !Number.isFinite(y)) {
|
|
2394
|
+
return {
|
|
2395
|
+
ok: false,
|
|
2396
|
+
error: {
|
|
2397
|
+
code: "EVALUATION_FAILED",
|
|
2398
|
+
message: "Invalid locator point coordinates.",
|
|
2399
|
+
retryable: false
|
|
2400
|
+
}
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
return { ok: true, point: { x, y } };
|
|
2404
|
+
}
|
|
2405
|
+
async performCdpType(tabId, options) {
|
|
2406
|
+
const targetPoint = await sendToTab(tabId, "drive.type_target_point", {
|
|
2407
|
+
locator: options.locator
|
|
2408
|
+
});
|
|
2409
|
+
if (!targetPoint.ok) {
|
|
2410
|
+
return targetPoint;
|
|
2411
|
+
}
|
|
2412
|
+
const payload = targetPoint.result;
|
|
2413
|
+
if (!payload || typeof payload !== "object") {
|
|
2414
|
+
return {
|
|
2415
|
+
ok: false,
|
|
2416
|
+
error: {
|
|
2417
|
+
code: "EVALUATION_FAILED",
|
|
2418
|
+
message: "Invalid type target payload.",
|
|
2419
|
+
retryable: false
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
const record = payload;
|
|
2424
|
+
const x = record.x;
|
|
2425
|
+
const y = record.y;
|
|
2426
|
+
if (typeof x !== "number" || !Number.isFinite(x) || typeof y !== "number" || !Number.isFinite(y)) {
|
|
2427
|
+
return {
|
|
2428
|
+
ok: false,
|
|
2429
|
+
error: {
|
|
2430
|
+
code: "EVALUATION_FAILED",
|
|
2431
|
+
message: "Invalid type target coordinates.",
|
|
2432
|
+
retryable: false
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
await this.dispatchCdpClick(tabId, x, y, 1);
|
|
2438
|
+
if (options.clear) {
|
|
2439
|
+
const clearResult = await sendToTab(
|
|
2440
|
+
tabId,
|
|
2441
|
+
"drive.clear_active_editable"
|
|
2442
|
+
);
|
|
2443
|
+
if (!clearResult.ok) {
|
|
2444
|
+
return clearResult;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
if (options.text.length > 0) {
|
|
2448
|
+
await this.sendDebuggerCommand(
|
|
2449
|
+
tabId,
|
|
2450
|
+
"Input.insertText",
|
|
2451
|
+
{ text: options.text },
|
|
2452
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
if (options.submit) {
|
|
2456
|
+
await this.dispatchCdpKeyPress(tabId, "Enter", void 0);
|
|
2457
|
+
}
|
|
2458
|
+
this.touchDebuggerSession(tabId);
|
|
2459
|
+
return { ok: true };
|
|
2460
|
+
} catch (error) {
|
|
2461
|
+
return {
|
|
2462
|
+
ok: false,
|
|
2463
|
+
error: mapDebuggerErrorMessage(
|
|
2464
|
+
error instanceof Error ? error.message : "Type dispatch failed."
|
|
2465
|
+
)
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
normalizeModifierMask(modifiers) {
|
|
2470
|
+
const MOD_ALT = 1;
|
|
2471
|
+
const MOD_CTRL = 2;
|
|
2472
|
+
const MOD_META = 4;
|
|
2473
|
+
const MOD_SHIFT = 8;
|
|
2474
|
+
let mask = 0;
|
|
2475
|
+
if (Array.isArray(modifiers)) {
|
|
2476
|
+
for (const modifier of modifiers) {
|
|
2477
|
+
if (typeof modifier !== "string") {
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
2480
|
+
const normalized = modifier.toLowerCase();
|
|
2481
|
+
if (normalized === "alt") {
|
|
2482
|
+
mask |= MOD_ALT;
|
|
2483
|
+
} else if (normalized === "ctrl") {
|
|
2484
|
+
mask |= MOD_CTRL;
|
|
2485
|
+
} else if (normalized === "meta") {
|
|
2486
|
+
mask |= MOD_META;
|
|
2487
|
+
} else if (normalized === "shift") {
|
|
2488
|
+
mask |= MOD_SHIFT;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
return mask;
|
|
2492
|
+
}
|
|
2493
|
+
if (!modifiers || typeof modifiers !== "object") {
|
|
2494
|
+
return mask;
|
|
2495
|
+
}
|
|
2496
|
+
const record = modifiers;
|
|
2497
|
+
if (record.alt) {
|
|
2498
|
+
mask |= MOD_ALT;
|
|
2499
|
+
}
|
|
2500
|
+
if (record.ctrl) {
|
|
2501
|
+
mask |= MOD_CTRL;
|
|
2502
|
+
}
|
|
2503
|
+
if (record.meta) {
|
|
2504
|
+
mask |= MOD_META;
|
|
2505
|
+
}
|
|
2506
|
+
if (record.shift) {
|
|
2507
|
+
mask |= MOD_SHIFT;
|
|
2508
|
+
}
|
|
2509
|
+
return mask;
|
|
2510
|
+
}
|
|
2511
|
+
keyToCode(key) {
|
|
2512
|
+
const map = {
|
|
2513
|
+
Enter: "Enter",
|
|
2514
|
+
Tab: "Tab",
|
|
2515
|
+
Escape: "Escape",
|
|
2516
|
+
Esc: "Escape",
|
|
2517
|
+
Backspace: "Backspace",
|
|
2518
|
+
Delete: "Delete",
|
|
2519
|
+
ArrowUp: "ArrowUp",
|
|
2520
|
+
ArrowDown: "ArrowDown",
|
|
2521
|
+
ArrowLeft: "ArrowLeft",
|
|
2522
|
+
ArrowRight: "ArrowRight",
|
|
2523
|
+
Home: "Home",
|
|
2524
|
+
End: "End",
|
|
2525
|
+
PageUp: "PageUp",
|
|
2526
|
+
PageDown: "PageDown",
|
|
2527
|
+
" ": "Space",
|
|
2528
|
+
Space: "Space"
|
|
2529
|
+
};
|
|
2530
|
+
if (map[key]) {
|
|
2531
|
+
return map[key];
|
|
2532
|
+
}
|
|
2533
|
+
if (key.length === 1) {
|
|
2534
|
+
if (/[a-zA-Z]/.test(key)) {
|
|
2535
|
+
return `Key${key.toUpperCase()}`;
|
|
2536
|
+
}
|
|
2537
|
+
if (/[0-9]/.test(key)) {
|
|
2538
|
+
return `Digit${key}`;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return key;
|
|
2542
|
+
}
|
|
2543
|
+
async dispatchCdpKeyPress(tabId, key, modifiers) {
|
|
2544
|
+
const code = this.keyToCode(key);
|
|
2545
|
+
const modifierMask = this.normalizeModifierMask(modifiers);
|
|
2546
|
+
const isTextInput = key.length === 1 && (modifierMask & (1 | 2 | 4)) === 0;
|
|
2547
|
+
const keyDownParams = {
|
|
2548
|
+
type: "keyDown",
|
|
2549
|
+
key,
|
|
2550
|
+
code,
|
|
2551
|
+
modifiers: modifierMask
|
|
2552
|
+
};
|
|
2553
|
+
if (isTextInput) {
|
|
2554
|
+
keyDownParams.text = key;
|
|
2555
|
+
keyDownParams.unmodifiedText = key;
|
|
2556
|
+
}
|
|
2557
|
+
await this.sendDebuggerCommand(
|
|
2558
|
+
tabId,
|
|
2559
|
+
"Input.dispatchKeyEvent",
|
|
2560
|
+
keyDownParams,
|
|
2561
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2562
|
+
);
|
|
2563
|
+
await this.sendDebuggerCommand(
|
|
2564
|
+
tabId,
|
|
2565
|
+
"Input.dispatchKeyEvent",
|
|
2566
|
+
{
|
|
2567
|
+
type: "keyUp",
|
|
2568
|
+
key,
|
|
2569
|
+
code,
|
|
2570
|
+
modifiers: modifierMask
|
|
2571
|
+
},
|
|
2572
|
+
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2573
|
+
);
|
|
2574
|
+
this.touchDebuggerSession(tabId);
|
|
2575
|
+
}
|
|
1859
2576
|
async handleDebuggerRequest(message) {
|
|
1860
2577
|
const respondAck = (result) => {
|
|
1861
2578
|
this.sendMessage({
|