@flrande/bak-extension 0.3.8 → 0.6.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/dist/.bak-e2e-build-stamp +1 -1
- package/dist/background.global.js +1305 -140
- package/dist/content.global.js +820 -3
- package/dist/manifest.json +2 -2
- package/package.json +2 -2
- package/public/manifest.json +2 -2
- package/src/background.ts +1762 -992
- package/src/content.ts +2419 -1593
- package/src/network-debugger.ts +495 -0
- package/src/privacy.ts +112 -1
- package/src/session-binding-storage.ts +68 -0
- package/src/workspace.ts +912 -917
package/dist/content.global.js
CHANGED
|
@@ -33,11 +33,24 @@
|
|
|
33
33
|
// src/privacy.ts
|
|
34
34
|
var MAX_SAFE_TEXT_LENGTH = 120;
|
|
35
35
|
var MAX_DEBUG_TEXT_LENGTH = 320;
|
|
36
|
+
var REDACTION_MARKER = "[REDACTED]";
|
|
36
37
|
var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
37
38
|
var LONG_DIGIT_PATTERN = /(?:\d[ -]?){13,19}/g;
|
|
38
39
|
var OTP_PATTERN = /^\d{4,8}$/;
|
|
39
40
|
var SECRET_QUERY_PARAM_PATTERN = /(token|secret|password|passwd|otp|code|session|auth)=/i;
|
|
40
41
|
var HIGH_ENTROPY_TOKEN_PATTERN = /^(?=.*\d)(?=.*[a-zA-Z])[A-Za-z0-9~!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`]{16,}$/;
|
|
42
|
+
var TRANSPORT_SECRET_KEY_SOURCE = "(?:api[_-]?key|authorization|auth|cookie|csrf(?:token)?|nonce|password|passwd|secret|session(?:id)?|token|xsrf(?:token)?)";
|
|
43
|
+
var TRANSPORT_SECRET_PAIR_PATTERN = new RegExp(`((?:^|[?&;,\\s])${TRANSPORT_SECRET_KEY_SOURCE}=)[^&\\r\\n"'>]*`, "gi");
|
|
44
|
+
var JSON_SECRET_VALUE_PATTERN = new RegExp(
|
|
45
|
+
`((?:"|')${TRANSPORT_SECRET_KEY_SOURCE}(?:"|')\\s*:\\s*)(?:"[^"]*"|'[^']*'|true|false|null|-?\\d+(?:\\.\\d+)?)`,
|
|
46
|
+
"gi"
|
|
47
|
+
);
|
|
48
|
+
var ASSIGNMENT_SECRET_VALUE_PATTERN = new RegExp(
|
|
49
|
+
`((?:^|[\\s,{;])${TRANSPORT_SECRET_KEY_SOURCE}\\s*[:=]\\s*)([^,&;}"'\\r\\n]+)`,
|
|
50
|
+
"gi"
|
|
51
|
+
);
|
|
52
|
+
var AUTHORIZATION_VALUE_PATTERN = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+\b/gi;
|
|
53
|
+
var SENSITIVE_ATTRIBUTE_PATTERN = /(?:api[_-]?key|authorization|auth|cookie|csrf|nonce|password|passwd|secret|session|token|xsrf)/i;
|
|
41
54
|
var INPUT_TEXT_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
42
55
|
"text",
|
|
43
56
|
"search",
|
|
@@ -73,11 +86,31 @@
|
|
|
73
86
|
if (OTP_PATTERN.test(output)) {
|
|
74
87
|
return "[REDACTED:otp]";
|
|
75
88
|
}
|
|
76
|
-
if (HIGH_ENTROPY_TOKEN_PATTERN.test(output) && !output.includes("
|
|
89
|
+
if (HIGH_ENTROPY_TOKEN_PATTERN.test(output) && !/[ =&:]/.test(output) && !output.includes("[REDACTED")) {
|
|
77
90
|
return "[REDACTED:secret]";
|
|
78
91
|
}
|
|
79
92
|
return output;
|
|
80
93
|
}
|
|
94
|
+
function redactTransportSecrets(text) {
|
|
95
|
+
let output = text;
|
|
96
|
+
output = output.replace(AUTHORIZATION_VALUE_PATTERN, "$1 [REDACTED]");
|
|
97
|
+
output = output.replace(TRANSPORT_SECRET_PAIR_PATTERN, "$1[REDACTED]");
|
|
98
|
+
output = output.replace(JSON_SECRET_VALUE_PATTERN, '$1"[REDACTED]"');
|
|
99
|
+
output = output.replace(ASSIGNMENT_SECRET_VALUE_PATTERN, "$1[REDACTED]");
|
|
100
|
+
if (HIGH_ENTROPY_TOKEN_PATTERN.test(output) && !/[ =&:]/.test(output) && !output.includes("[REDACTED")) {
|
|
101
|
+
return "[REDACTED:secret]";
|
|
102
|
+
}
|
|
103
|
+
return output;
|
|
104
|
+
}
|
|
105
|
+
function redactAttributeValue(name, value) {
|
|
106
|
+
if (!value) {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
if (name === "value") {
|
|
110
|
+
return REDACTION_MARKER;
|
|
111
|
+
}
|
|
112
|
+
return redactTransportText(value);
|
|
113
|
+
}
|
|
81
114
|
function redactElementText(raw, options = {}) {
|
|
82
115
|
if (!raw) {
|
|
83
116
|
return "";
|
|
@@ -89,6 +122,44 @@
|
|
|
89
122
|
const redacted = redactByPattern(normalized);
|
|
90
123
|
return clamp(redacted, options);
|
|
91
124
|
}
|
|
125
|
+
function redactTransportText(raw) {
|
|
126
|
+
if (!raw) {
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
return redactTransportSecrets(String(raw));
|
|
130
|
+
}
|
|
131
|
+
function redactHtmlSnapshot(root) {
|
|
132
|
+
if (!root || !("cloneNode" in root)) {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
const clone = root.cloneNode(true);
|
|
136
|
+
const elements = [clone, ...Array.from(clone.querySelectorAll("*"))];
|
|
137
|
+
for (const element of elements) {
|
|
138
|
+
const tagName = element.tagName.toLowerCase();
|
|
139
|
+
if (tagName === "script" && !element.getAttribute("src")) {
|
|
140
|
+
element.textContent = "[REDACTED:script]";
|
|
141
|
+
}
|
|
142
|
+
if (tagName === "textarea" && (element.textContent ?? "").trim().length > 0) {
|
|
143
|
+
element.textContent = REDACTION_MARKER;
|
|
144
|
+
}
|
|
145
|
+
for (const attribute of Array.from(element.attributes)) {
|
|
146
|
+
const name = attribute.name;
|
|
147
|
+
const value = attribute.value;
|
|
148
|
+
const shouldRedactValue = name === "value" && (tagName === "input" || tagName === "textarea" || tagName === "option") || SENSITIVE_ATTRIBUTE_PATTERN.test(name);
|
|
149
|
+
if (shouldRedactValue) {
|
|
150
|
+
element.setAttribute(name, redactAttributeValue(name, value));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (name === "href" || name === "src" || name === "action" || name === "content" || name.startsWith("data-")) {
|
|
154
|
+
const redacted = redactAttributeValue(name, value);
|
|
155
|
+
if (redacted !== value) {
|
|
156
|
+
element.setAttribute(name, redacted);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return "outerHTML" in clone ? clone.outerHTML : "";
|
|
162
|
+
}
|
|
92
163
|
function isTextEntryField(candidates) {
|
|
93
164
|
const tag = candidates.tag.toLowerCase();
|
|
94
165
|
const role = (candidates.role ?? "").toLowerCase();
|
|
@@ -163,6 +234,8 @@
|
|
|
163
234
|
var longTaskCount = 0;
|
|
164
235
|
var longTaskDurationMs = 0;
|
|
165
236
|
var performanceBaselineMs = 0;
|
|
237
|
+
var pageLoadedAt = Math.round(performance.timeOrigin || Date.now());
|
|
238
|
+
var lastMutationAt = Date.now();
|
|
166
239
|
function isHtmlElement(node) {
|
|
167
240
|
if (!node) {
|
|
168
241
|
return false;
|
|
@@ -710,6 +783,28 @@
|
|
|
710
783
|
}
|
|
711
784
|
return collected;
|
|
712
785
|
}
|
|
786
|
+
function evaluateXPath(root, expression) {
|
|
787
|
+
try {
|
|
788
|
+
const ownerDocument = isDocumentNode(root) ? root : root.ownerDocument ?? document;
|
|
789
|
+
const iterator = ownerDocument.evaluate(
|
|
790
|
+
expression,
|
|
791
|
+
root,
|
|
792
|
+
null,
|
|
793
|
+
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
|
794
|
+
null
|
|
795
|
+
);
|
|
796
|
+
const matches = [];
|
|
797
|
+
for (let index = 0; index < iterator.snapshotLength; index += 1) {
|
|
798
|
+
const node = iterator.snapshotItem(index);
|
|
799
|
+
if (isHtmlElement(node)) {
|
|
800
|
+
matches.push(node);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return matches;
|
|
804
|
+
} catch {
|
|
805
|
+
return [];
|
|
806
|
+
}
|
|
807
|
+
}
|
|
713
808
|
function resolveFrameDocument(framePath) {
|
|
714
809
|
let currentDocument = document;
|
|
715
810
|
for (const selector of framePath) {
|
|
@@ -782,6 +877,7 @@
|
|
|
782
877
|
const eid = buildEid(element);
|
|
783
878
|
const selectors = {
|
|
784
879
|
css: toCssSelector(element),
|
|
880
|
+
xpath: null,
|
|
785
881
|
text: text || name ? (text || name).slice(0, 80) : null,
|
|
786
882
|
aria: role && name ? `${role}:${name.slice(0, 80)}` : null
|
|
787
883
|
};
|
|
@@ -793,6 +889,8 @@
|
|
|
793
889
|
role,
|
|
794
890
|
name,
|
|
795
891
|
text,
|
|
892
|
+
visible: isElementVisible(element),
|
|
893
|
+
enabled: !(element.disabled || element.getAttribute("aria-disabled") === "true"),
|
|
796
894
|
bbox: {
|
|
797
895
|
x: rect.x,
|
|
798
896
|
y: rect.y,
|
|
@@ -903,6 +1001,13 @@
|
|
|
903
1001
|
currentRoot = found.shadowRoot;
|
|
904
1002
|
}
|
|
905
1003
|
}
|
|
1004
|
+
if (locator.xpath) {
|
|
1005
|
+
const matches = evaluateXPath(root, locator.xpath).filter((item) => isElementVisible(item));
|
|
1006
|
+
const found = indexedCandidate(matches, locator);
|
|
1007
|
+
if (found) {
|
|
1008
|
+
return found;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
906
1011
|
if (locator.name) {
|
|
907
1012
|
const fallback = indexedCandidate(
|
|
908
1013
|
interactive.filter((element) => inferName(element).toLowerCase().includes(locator.name.toLowerCase())),
|
|
@@ -914,6 +1019,61 @@
|
|
|
914
1019
|
}
|
|
915
1020
|
return null;
|
|
916
1021
|
}
|
|
1022
|
+
function matchedElementsForLocator(locator) {
|
|
1023
|
+
if (!locator) {
|
|
1024
|
+
return [];
|
|
1025
|
+
}
|
|
1026
|
+
const rootResult = resolveRootForLocator(locator);
|
|
1027
|
+
if (!rootResult.ok) {
|
|
1028
|
+
return [];
|
|
1029
|
+
}
|
|
1030
|
+
const root = rootResult.root;
|
|
1031
|
+
const interactive = getInteractiveElements(root, locator.shadow !== "none");
|
|
1032
|
+
if (locator.css) {
|
|
1033
|
+
const parts = splitShadowSelector(locator.css);
|
|
1034
|
+
let currentRoot = root;
|
|
1035
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
1036
|
+
const selector = parts[index];
|
|
1037
|
+
const found = locator.shadow === "none" ? querySelectorInTree(currentRoot, selector) : querySelectorAcrossOpenShadow(currentRoot, selector);
|
|
1038
|
+
if (!found) {
|
|
1039
|
+
return [];
|
|
1040
|
+
}
|
|
1041
|
+
if (index === parts.length - 1) {
|
|
1042
|
+
return (locator.shadow === "none" ? Array.from(currentRoot.querySelectorAll(selector)) : querySelectorAllAcrossOpenShadow(currentRoot, selector)).filter((item) => isElementVisible(item));
|
|
1043
|
+
}
|
|
1044
|
+
if (!found.shadowRoot) {
|
|
1045
|
+
return [];
|
|
1046
|
+
}
|
|
1047
|
+
currentRoot = found.shadowRoot;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (locator.xpath) {
|
|
1051
|
+
return evaluateXPath(root, locator.xpath).filter((item) => isElementVisible(item));
|
|
1052
|
+
}
|
|
1053
|
+
if (locator.role || locator.name || locator.text) {
|
|
1054
|
+
return interactive.filter((element) => {
|
|
1055
|
+
if (locator.role && inferRole(element).toLowerCase() !== locator.role.toLowerCase()) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
if (locator.name && !inferName(element).toLowerCase().includes(locator.name.toLowerCase())) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
if (locator.text) {
|
|
1062
|
+
const text = (element.innerText || element.textContent || "").toLowerCase();
|
|
1063
|
+
if (!text.includes(locator.text.toLowerCase())) {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return true;
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
if (locator.eid) {
|
|
1071
|
+
collectElements({}, locator);
|
|
1072
|
+
const fromCache = elementCache.get(locator.eid);
|
|
1073
|
+
return fromCache ? [fromCache] : [];
|
|
1074
|
+
}
|
|
1075
|
+
return [];
|
|
1076
|
+
}
|
|
917
1077
|
function ensureOverlayRoot() {
|
|
918
1078
|
let root = document.getElementById("bak-overlay-root");
|
|
919
1079
|
if (!root) {
|
|
@@ -1387,6 +1547,30 @@
|
|
|
1387
1547
|
return true;
|
|
1388
1548
|
}).slice(-limit).reverse();
|
|
1389
1549
|
}
|
|
1550
|
+
function filterNetworkEntrySections(entry, include) {
|
|
1551
|
+
if (!Array.isArray(include)) {
|
|
1552
|
+
return entry;
|
|
1553
|
+
}
|
|
1554
|
+
const sections = new Set(
|
|
1555
|
+
include.map(String).filter((section) => section === "request" || section === "response")
|
|
1556
|
+
);
|
|
1557
|
+
if (sections.size === 0 || sections.size === 2) {
|
|
1558
|
+
return entry;
|
|
1559
|
+
}
|
|
1560
|
+
const clone = { ...entry };
|
|
1561
|
+
if (!sections.has("request")) {
|
|
1562
|
+
delete clone.requestHeaders;
|
|
1563
|
+
delete clone.requestBodyPreview;
|
|
1564
|
+
delete clone.requestBodyTruncated;
|
|
1565
|
+
}
|
|
1566
|
+
if (!sections.has("response")) {
|
|
1567
|
+
delete clone.responseHeaders;
|
|
1568
|
+
delete clone.responseBodyPreview;
|
|
1569
|
+
delete clone.responseBodyTruncated;
|
|
1570
|
+
delete clone.binary;
|
|
1571
|
+
}
|
|
1572
|
+
return clone;
|
|
1573
|
+
}
|
|
1390
1574
|
async function waitForNetwork(params) {
|
|
1391
1575
|
const timeoutMs = typeof params.timeoutMs === "number" ? Math.max(1, params.timeoutMs) : 5e3;
|
|
1392
1576
|
const sinceTs = typeof params.sinceTs === "number" ? params.sinceTs : Date.now() - timeoutMs;
|
|
@@ -1400,6 +1584,559 @@
|
|
|
1400
1584
|
}
|
|
1401
1585
|
throw { code: "E_TIMEOUT", message: "network.waitFor timeout" };
|
|
1402
1586
|
}
|
|
1587
|
+
function trackMutations() {
|
|
1588
|
+
const observer = new MutationObserver(() => {
|
|
1589
|
+
lastMutationAt = Date.now();
|
|
1590
|
+
});
|
|
1591
|
+
const root = document.documentElement ?? document.body;
|
|
1592
|
+
if (!root) {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
observer.observe(root, {
|
|
1596
|
+
childList: true,
|
|
1597
|
+
subtree: true,
|
|
1598
|
+
attributes: true,
|
|
1599
|
+
characterData: true
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
function currentContextSnapshot() {
|
|
1603
|
+
return {
|
|
1604
|
+
tabId: null,
|
|
1605
|
+
framePath: [...contextState.framePath],
|
|
1606
|
+
shadowPath: [...contextState.shadowPath]
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function timestampCandidatesFromText(text, patterns) {
|
|
1610
|
+
const regexes = (patterns ?? [String.raw`\b20\d{2}-\d{2}-\d{2}\b`, String.raw`\b20\d{2}\/\d{2}\/\d{2}\b`]).map(
|
|
1611
|
+
(pattern) => new RegExp(pattern, "gi")
|
|
1612
|
+
);
|
|
1613
|
+
const collected = /* @__PURE__ */ new Set();
|
|
1614
|
+
for (const regex of regexes) {
|
|
1615
|
+
for (const match of text.matchAll(regex)) {
|
|
1616
|
+
if (match[0]) {
|
|
1617
|
+
collected.add(match[0]);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
return [...collected];
|
|
1622
|
+
}
|
|
1623
|
+
function listInlineScripts() {
|
|
1624
|
+
return Array.from(document.scripts).filter((script) => !script.src).map((script) => {
|
|
1625
|
+
const content = script.textContent ?? "";
|
|
1626
|
+
const suspectedVars = [...content.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*(?:data|table|json|state|store)[A-Za-z_$\w]*)/gi)].map(
|
|
1627
|
+
(match) => match[1] ?? ""
|
|
1628
|
+
).filter(Boolean);
|
|
1629
|
+
return { content, suspectedVars };
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
function cookieMetadata() {
|
|
1633
|
+
return document.cookie.split(";").map((item) => item.trim()).filter(Boolean).map((item) => ({ name: item.split("=")[0] ?? "" })).filter((item) => item.name.length > 0);
|
|
1634
|
+
}
|
|
1635
|
+
function storageMetadata() {
|
|
1636
|
+
return {
|
|
1637
|
+
localStorageKeys: Object.keys(localStorage),
|
|
1638
|
+
sessionStorageKeys: Object.keys(sessionStorage)
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function collectFrames(baseDocument = document, framePath = []) {
|
|
1642
|
+
const frames = [];
|
|
1643
|
+
const frameNodes = Array.from(baseDocument.querySelectorAll("iframe,frame"));
|
|
1644
|
+
for (const frame of frameNodes) {
|
|
1645
|
+
if (!isFrameElement(frame)) {
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
const selector = frame.id ? `#${frame.id}` : `${frame.tagName.toLowerCase()}:nth-of-type(${frameNodes.indexOf(frame) + 1})`;
|
|
1649
|
+
try {
|
|
1650
|
+
const childDocument = frame.contentDocument;
|
|
1651
|
+
if (!childDocument) {
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
const nextPath = [...framePath, selector];
|
|
1655
|
+
frames.push({
|
|
1656
|
+
framePath: nextPath,
|
|
1657
|
+
url: childDocument.location.href
|
|
1658
|
+
});
|
|
1659
|
+
frames.push(...collectFrames(childDocument, nextPath));
|
|
1660
|
+
} catch {
|
|
1661
|
+
continue;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return frames;
|
|
1665
|
+
}
|
|
1666
|
+
function buildTableId(kind, index) {
|
|
1667
|
+
return `${kind}:${index + 1}`;
|
|
1668
|
+
}
|
|
1669
|
+
function describeTables() {
|
|
1670
|
+
const rootResult = resolveRootForLocator();
|
|
1671
|
+
if (!rootResult.ok) {
|
|
1672
|
+
return [];
|
|
1673
|
+
}
|
|
1674
|
+
const root = rootResult.root;
|
|
1675
|
+
const tables = [];
|
|
1676
|
+
const htmlTables = Array.from(root.querySelectorAll("table"));
|
|
1677
|
+
for (const [index, table] of htmlTables.entries()) {
|
|
1678
|
+
tables.push({
|
|
1679
|
+
id: buildTableId(table.closest(".dataTables_wrapper") ? "dataTables" : "html", index),
|
|
1680
|
+
name: (table.getAttribute("aria-label") || table.getAttribute("data-testid") || table.id || `table-${index + 1}`).trim(),
|
|
1681
|
+
kind: table.closest(".dataTables_wrapper") ? "dataTables" : "html",
|
|
1682
|
+
selector: table.id ? `#${table.id}` : void 0,
|
|
1683
|
+
rowCount: table.querySelectorAll("tbody tr").length || table.querySelectorAll("tr").length,
|
|
1684
|
+
columnCount: table.querySelectorAll("thead th").length || table.querySelectorAll("tr:first-child th, tr:first-child td").length
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
const gridRoots = Array.from(root.querySelectorAll('[role="grid"], [role="table"], .ag-root, .ag-root-wrapper'));
|
|
1688
|
+
for (const [index, grid] of gridRoots.entries()) {
|
|
1689
|
+
const kind = grid.className.includes("ag-") ? "ag-grid" : "aria-grid";
|
|
1690
|
+
tables.push({
|
|
1691
|
+
id: buildTableId(kind, index),
|
|
1692
|
+
name: (grid.getAttribute("aria-label") || grid.getAttribute("data-testid") || grid.id || `grid-${index + 1}`).trim(),
|
|
1693
|
+
kind,
|
|
1694
|
+
selector: grid.id ? `#${grid.id}` : void 0,
|
|
1695
|
+
rowCount: grid.querySelectorAll('[role="row"]').length,
|
|
1696
|
+
columnCount: grid.querySelectorAll('[role="columnheader"]').length
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
return tables;
|
|
1700
|
+
}
|
|
1701
|
+
function resolveTable(handleId) {
|
|
1702
|
+
const tables = describeTables();
|
|
1703
|
+
const handle = tables.find((candidate) => candidate.id === handleId);
|
|
1704
|
+
if (!handle) {
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
const rootResult = resolveRootForLocator();
|
|
1708
|
+
if (!rootResult.ok) {
|
|
1709
|
+
return null;
|
|
1710
|
+
}
|
|
1711
|
+
const root = rootResult.root;
|
|
1712
|
+
if (!handle.selector) {
|
|
1713
|
+
if (handle.kind === "html" || handle.kind === "dataTables") {
|
|
1714
|
+
const index2 = Number(handle.id.split(":")[1] ?? "1") - 1;
|
|
1715
|
+
return { table: handle, element: root.querySelectorAll("table")[index2] ?? null };
|
|
1716
|
+
}
|
|
1717
|
+
const candidates = root.querySelectorAll('[role="grid"], [role="table"], .ag-root, .ag-root-wrapper');
|
|
1718
|
+
const index = Number(handle.id.split(":")[1] ?? "1") - 1;
|
|
1719
|
+
return { table: handle, element: candidates[index] ?? null };
|
|
1720
|
+
}
|
|
1721
|
+
return { table: handle, element: root.querySelector(handle.selector) };
|
|
1722
|
+
}
|
|
1723
|
+
function htmlTableSchema(table) {
|
|
1724
|
+
const headers = Array.from(table.querySelectorAll("thead th"));
|
|
1725
|
+
if (headers.length > 0) {
|
|
1726
|
+
return headers.map((cell, index) => ({
|
|
1727
|
+
key: `col_${index + 1}`,
|
|
1728
|
+
label: (cell.textContent ?? "").trim() || `Column ${index + 1}`
|
|
1729
|
+
}));
|
|
1730
|
+
}
|
|
1731
|
+
const firstRow = table.querySelector("tr");
|
|
1732
|
+
return Array.from(firstRow?.querySelectorAll("th,td") ?? []).map((cell, index) => ({
|
|
1733
|
+
key: `col_${index + 1}`,
|
|
1734
|
+
label: (cell.textContent ?? "").trim() || `Column ${index + 1}`
|
|
1735
|
+
}));
|
|
1736
|
+
}
|
|
1737
|
+
function htmlTableRows(table, limit) {
|
|
1738
|
+
const columns = htmlTableSchema(table);
|
|
1739
|
+
const rowNodes = Array.from(table.querySelectorAll("tbody tr")).length > 0 ? Array.from(table.querySelectorAll("tbody tr")) : Array.from(table.querySelectorAll("tr")).slice(1);
|
|
1740
|
+
return rowNodes.slice(0, limit).map((row) => {
|
|
1741
|
+
const record = {};
|
|
1742
|
+
Array.from(row.querySelectorAll("th,td")).forEach((cell, index) => {
|
|
1743
|
+
const key = columns[index]?.label ?? `Column ${index + 1}`;
|
|
1744
|
+
record[key] = (cell.textContent ?? "").trim();
|
|
1745
|
+
});
|
|
1746
|
+
return record;
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
function gridSchema(grid) {
|
|
1750
|
+
return Array.from(grid.querySelectorAll('[role="columnheader"]')).map((cell, index) => ({
|
|
1751
|
+
key: `col_${index + 1}`,
|
|
1752
|
+
label: (cell.textContent ?? "").trim() || `Column ${index + 1}`
|
|
1753
|
+
}));
|
|
1754
|
+
}
|
|
1755
|
+
function gridRows(grid, limit) {
|
|
1756
|
+
const columns = gridSchema(grid);
|
|
1757
|
+
return Array.from(grid.querySelectorAll('[role="row"]')).slice(0, limit).map((row) => {
|
|
1758
|
+
const record = {};
|
|
1759
|
+
Array.from(row.querySelectorAll('[role="gridcell"], [role="cell"], [role="columnheader"]')).forEach((cell, index) => {
|
|
1760
|
+
const key = columns[index]?.label ?? `Column ${index + 1}`;
|
|
1761
|
+
record[key] = (cell.textContent ?? "").trim();
|
|
1762
|
+
});
|
|
1763
|
+
return record;
|
|
1764
|
+
}).filter((row) => Object.values(row).some((value) => String(value).trim().length > 0));
|
|
1765
|
+
}
|
|
1766
|
+
function runPageWorldRequest(action, payload) {
|
|
1767
|
+
const requestId = `bak_page_world_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
1768
|
+
return new Promise((resolve, reject) => {
|
|
1769
|
+
const timeoutMs = typeof payload.timeoutMs === "number" && Number.isFinite(payload.timeoutMs) && payload.timeoutMs > 0 ? Math.max(1e3, Math.floor(payload.timeoutMs) + 1e3) : 15e3;
|
|
1770
|
+
const responseNode = document.createElement("div");
|
|
1771
|
+
responseNode.setAttribute("data-bak-page-world-response", requestId);
|
|
1772
|
+
responseNode.hidden = true;
|
|
1773
|
+
(document.documentElement ?? document.head ?? document.body).appendChild(responseNode);
|
|
1774
|
+
const cleanup = () => {
|
|
1775
|
+
clearTimeout(timer);
|
|
1776
|
+
observer.disconnect();
|
|
1777
|
+
responseNode.remove();
|
|
1778
|
+
};
|
|
1779
|
+
const tryResolveFromNode = () => {
|
|
1780
|
+
if (responseNode.getAttribute("data-ready") !== "1") {
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const payloadText = responseNode.getAttribute("data-payload");
|
|
1784
|
+
const detail = payloadText ? JSON.parse(payloadText) : null;
|
|
1785
|
+
if (!detail) {
|
|
1786
|
+
cleanup();
|
|
1787
|
+
reject(failAction("E_EXECUTION", `${action} returned no detail`));
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
cleanup();
|
|
1791
|
+
if (detail.ok) {
|
|
1792
|
+
resolve(detail.result);
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
reject(detail.error ?? failAction("E_EXECUTION", `${action} failed`));
|
|
1796
|
+
};
|
|
1797
|
+
const observer = new MutationObserver(() => {
|
|
1798
|
+
try {
|
|
1799
|
+
tryResolveFromNode();
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
cleanup();
|
|
1802
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
observer.observe(responseNode, {
|
|
1806
|
+
attributes: true,
|
|
1807
|
+
childList: true,
|
|
1808
|
+
characterData: true,
|
|
1809
|
+
subtree: true
|
|
1810
|
+
});
|
|
1811
|
+
const timer = window.setTimeout(() => {
|
|
1812
|
+
cleanup();
|
|
1813
|
+
reject(failAction("E_TIMEOUT", `${action} timed out`));
|
|
1814
|
+
}, timeoutMs);
|
|
1815
|
+
const injector = document.createElement("script");
|
|
1816
|
+
injector.textContent = `
|
|
1817
|
+
(() => {
|
|
1818
|
+
const requestId = ${JSON.stringify(requestId)};
|
|
1819
|
+
const action = ${JSON.stringify(action)};
|
|
1820
|
+
const payload = ${JSON.stringify(payload)};
|
|
1821
|
+
const responseNode = document.querySelector('div[data-bak-page-world-response="' + requestId + '"]');
|
|
1822
|
+
const emit = (detail) => {
|
|
1823
|
+
if (!responseNode) {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
responseNode.setAttribute('data-payload', JSON.stringify(detail));
|
|
1827
|
+
responseNode.setAttribute('data-ready', '1');
|
|
1828
|
+
};
|
|
1829
|
+
const serializeValue = (value, maxBytes) => {
|
|
1830
|
+
let cloned;
|
|
1831
|
+
try {
|
|
1832
|
+
cloned = typeof structuredClone === 'function' ? structuredClone(value) : JSON.parse(JSON.stringify(value));
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
throw { code: 'E_NOT_SERIALIZABLE', message: error instanceof Error ? error.message : String(error) };
|
|
1835
|
+
}
|
|
1836
|
+
const json = JSON.stringify(cloned);
|
|
1837
|
+
if (typeof maxBytes === 'number' && maxBytes > 0 && json.length > maxBytes) {
|
|
1838
|
+
throw { code: 'E_BODY_TOO_LARGE', message: 'serialized value exceeds max-bytes', details: { bytes: json.length, maxBytes } };
|
|
1839
|
+
}
|
|
1840
|
+
return { value: cloned, bytes: json.length };
|
|
1841
|
+
};
|
|
1842
|
+
const resolveFrameWindow = (framePath) => {
|
|
1843
|
+
let currentWindow = window;
|
|
1844
|
+
let currentDocument = document;
|
|
1845
|
+
for (const selector of framePath || []) {
|
|
1846
|
+
const frame = currentDocument.querySelector(selector);
|
|
1847
|
+
if (!frame || !('contentWindow' in frame)) {
|
|
1848
|
+
throw { code: 'E_NOT_FOUND', message: 'frame not found: ' + selector };
|
|
1849
|
+
}
|
|
1850
|
+
const nextWindow = frame.contentWindow;
|
|
1851
|
+
if (!nextWindow) {
|
|
1852
|
+
throw { code: 'E_NOT_READY', message: 'frame window unavailable: ' + selector };
|
|
1853
|
+
}
|
|
1854
|
+
try {
|
|
1855
|
+
currentDocument = nextWindow.document;
|
|
1856
|
+
} catch {
|
|
1857
|
+
throw { code: 'E_CROSS_ORIGIN_BLOCKED', message: 'cross-origin frame is not accessible: ' + selector };
|
|
1858
|
+
}
|
|
1859
|
+
currentWindow = nextWindow;
|
|
1860
|
+
}
|
|
1861
|
+
return currentWindow;
|
|
1862
|
+
};
|
|
1863
|
+
const collectFrames = (rootWindow, framePath) => {
|
|
1864
|
+
const collected = [{
|
|
1865
|
+
url: rootWindow.location.href,
|
|
1866
|
+
framePath,
|
|
1867
|
+
targetWindow: rootWindow
|
|
1868
|
+
}];
|
|
1869
|
+
const frames = Array.from(rootWindow.document.querySelectorAll('iframe,frame'));
|
|
1870
|
+
frames.forEach((frame, index) => {
|
|
1871
|
+
const selector = frame.id ? '#' + frame.id : frame.tagName.toLowerCase() + ':nth-of-type(' + (index + 1) + ')';
|
|
1872
|
+
try {
|
|
1873
|
+
if (!frame.contentWindow || !frame.contentWindow.document) {
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
collected.push(...collectFrames(frame.contentWindow, [...framePath, selector]));
|
|
1877
|
+
} catch {
|
|
1878
|
+
// Skip cross-origin frames.
|
|
1879
|
+
}
|
|
1880
|
+
});
|
|
1881
|
+
return collected;
|
|
1882
|
+
};
|
|
1883
|
+
const parsePath = (path) => {
|
|
1884
|
+
if (typeof path !== 'string' || !path.trim()) {
|
|
1885
|
+
throw { code: 'E_INVALID_PARAMS', message: 'path is required' };
|
|
1886
|
+
}
|
|
1887
|
+
const normalized = path.replace(/^globalThis\\.?/, '').replace(/^window\\.?/, '').trim();
|
|
1888
|
+
if (!normalized) {
|
|
1889
|
+
return [];
|
|
1890
|
+
}
|
|
1891
|
+
const segments = [];
|
|
1892
|
+
let index = 0;
|
|
1893
|
+
while (index < normalized.length) {
|
|
1894
|
+
if (normalized[index] === '.') {
|
|
1895
|
+
index += 1;
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
if (normalized[index] === '[') {
|
|
1899
|
+
const bracket = normalized.slice(index).match(/^\\[(\\d+)\\]/);
|
|
1900
|
+
if (!bracket) {
|
|
1901
|
+
throw { code: 'E_INVALID_PARAMS', message: 'Only numeric bracket paths are supported' };
|
|
1902
|
+
}
|
|
1903
|
+
segments.push(Number(bracket[1]));
|
|
1904
|
+
index += bracket[0].length;
|
|
1905
|
+
continue;
|
|
1906
|
+
}
|
|
1907
|
+
const identifier = normalized.slice(index).match(/^[A-Za-z_$][\\w$]*/);
|
|
1908
|
+
if (!identifier) {
|
|
1909
|
+
throw { code: 'E_INVALID_PARAMS', message: 'Unsupported path token near: ' + normalized.slice(index, index + 16) };
|
|
1910
|
+
}
|
|
1911
|
+
segments.push(identifier[0]);
|
|
1912
|
+
index += identifier[0].length;
|
|
1913
|
+
}
|
|
1914
|
+
return segments;
|
|
1915
|
+
};
|
|
1916
|
+
const readPath = (targetWindow, path) => {
|
|
1917
|
+
const segments = parsePath(path);
|
|
1918
|
+
let current = targetWindow;
|
|
1919
|
+
for (const segment of segments) {
|
|
1920
|
+
if (current == null || !(segment in current)) {
|
|
1921
|
+
throw { code: 'E_NOT_FOUND', message: 'path not found: ' + path };
|
|
1922
|
+
}
|
|
1923
|
+
current = current[segment];
|
|
1924
|
+
}
|
|
1925
|
+
return current;
|
|
1926
|
+
};
|
|
1927
|
+
const buildScopeTargets = () => {
|
|
1928
|
+
if (payload.scope === 'main') {
|
|
1929
|
+
return [{
|
|
1930
|
+
url: window.location.href,
|
|
1931
|
+
framePath: [],
|
|
1932
|
+
targetWindow: window
|
|
1933
|
+
}];
|
|
1934
|
+
}
|
|
1935
|
+
if (payload.scope === 'all-frames') {
|
|
1936
|
+
return collectFrames(window, []);
|
|
1937
|
+
}
|
|
1938
|
+
return [{
|
|
1939
|
+
url: resolveFrameWindow(payload.framePath || []).location.href,
|
|
1940
|
+
framePath: payload.framePath || [],
|
|
1941
|
+
targetWindow: resolveFrameWindow(payload.framePath || [])
|
|
1942
|
+
}];
|
|
1943
|
+
};
|
|
1944
|
+
const toResult = async (target) => {
|
|
1945
|
+
if (action === 'globals') {
|
|
1946
|
+
const keys = Object.keys(target.targetWindow).filter((key) => /(data|table|json|state|store)/i.test(key)).slice(0, 50);
|
|
1947
|
+
return { url: target.url, framePath: target.framePath, value: keys };
|
|
1948
|
+
}
|
|
1949
|
+
if (action === 'eval') {
|
|
1950
|
+
const serialized = serializeValue(target.targetWindow.eval(payload.expr), payload.maxBytes);
|
|
1951
|
+
return { url: target.url, framePath: target.framePath, value: serialized.value, bytes: serialized.bytes };
|
|
1952
|
+
}
|
|
1953
|
+
if (action === 'extract') {
|
|
1954
|
+
const serialized = serializeValue(readPath(target.targetWindow, payload.path), payload.maxBytes);
|
|
1955
|
+
return { url: target.url, framePath: target.framePath, value: serialized.value, bytes: serialized.bytes };
|
|
1956
|
+
}
|
|
1957
|
+
if (action === 'fetch') {
|
|
1958
|
+
const headers = { ...(payload.headers || {}) };
|
|
1959
|
+
if (payload.contentType && !headers['Content-Type']) {
|
|
1960
|
+
headers['Content-Type'] = payload.contentType;
|
|
1961
|
+
}
|
|
1962
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
1963
|
+
const timeoutId =
|
|
1964
|
+
controller && typeof payload.timeoutMs === 'number' && payload.timeoutMs > 0
|
|
1965
|
+
? window.setTimeout(() => controller.abort(new Error('fetch timeout')), payload.timeoutMs)
|
|
1966
|
+
: null;
|
|
1967
|
+
let response;
|
|
1968
|
+
try {
|
|
1969
|
+
response = await target.targetWindow.fetch(payload.url, {
|
|
1970
|
+
method: payload.method || 'GET',
|
|
1971
|
+
headers,
|
|
1972
|
+
body: typeof payload.body === 'string' ? payload.body : undefined,
|
|
1973
|
+
signal: controller ? controller.signal : undefined
|
|
1974
|
+
});
|
|
1975
|
+
} finally {
|
|
1976
|
+
if (timeoutId !== null) {
|
|
1977
|
+
window.clearTimeout(timeoutId);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
const headerMap = {};
|
|
1981
|
+
response.headers.forEach((value, key) => {
|
|
1982
|
+
headerMap[key] = value;
|
|
1983
|
+
});
|
|
1984
|
+
const bodyText = await response.text();
|
|
1985
|
+
const encoder = typeof TextEncoder === 'function' ? new TextEncoder() : null;
|
|
1986
|
+
const decoder = typeof TextDecoder === 'function' ? new TextDecoder() : null;
|
|
1987
|
+
const previewLimit = typeof payload.maxBytes === 'number' && payload.maxBytes > 0 ? payload.maxBytes : 8192;
|
|
1988
|
+
const encodedBody = encoder ? encoder.encode(bodyText) : null;
|
|
1989
|
+
const bodyBytes = encodedBody ? encodedBody.byteLength : bodyText.length;
|
|
1990
|
+
const truncated = bodyBytes > previewLimit;
|
|
1991
|
+
const finalBodyText =
|
|
1992
|
+
encodedBody && decoder
|
|
1993
|
+
? decoder.decode(encodedBody.subarray(0, Math.min(encodedBody.byteLength, previewLimit)))
|
|
1994
|
+
: truncated
|
|
1995
|
+
? bodyText.slice(0, previewLimit)
|
|
1996
|
+
: bodyText;
|
|
1997
|
+
if (payload.mode === 'json' && truncated) {
|
|
1998
|
+
throw {
|
|
1999
|
+
code: 'E_BODY_TOO_LARGE',
|
|
2000
|
+
message: 'JSON response exceeds max-bytes',
|
|
2001
|
+
details: {
|
|
2002
|
+
bytes: bodyBytes,
|
|
2003
|
+
maxBytes: previewLimit
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
const result = {
|
|
2008
|
+
url: response.url,
|
|
2009
|
+
status: response.status,
|
|
2010
|
+
ok: response.ok,
|
|
2011
|
+
headers: headerMap,
|
|
2012
|
+
contentType: response.headers.get('content-type') || undefined,
|
|
2013
|
+
bodyText: payload.mode === 'json' ? undefined : finalBodyText,
|
|
2014
|
+
json: payload.mode === 'json' && bodyText ? JSON.parse(bodyText) : undefined,
|
|
2015
|
+
bytes: bodyBytes,
|
|
2016
|
+
truncated
|
|
2017
|
+
};
|
|
2018
|
+
return { url: target.url, framePath: target.framePath, value: result };
|
|
2019
|
+
}
|
|
2020
|
+
throw { code: 'E_NOT_FOUND', message: 'Unsupported page-world action: ' + action };
|
|
2021
|
+
};
|
|
2022
|
+
Promise.resolve()
|
|
2023
|
+
.then(async () => {
|
|
2024
|
+
const targets = buildScopeTargets();
|
|
2025
|
+
const results = [];
|
|
2026
|
+
for (const target of targets) {
|
|
2027
|
+
try {
|
|
2028
|
+
results.push(await toResult(target));
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
results.push({
|
|
2031
|
+
url: target.url,
|
|
2032
|
+
framePath: target.framePath,
|
|
2033
|
+
error: typeof error === 'object' && error !== null && 'code' in error
|
|
2034
|
+
? error
|
|
2035
|
+
: { code: 'E_EXECUTION', message: error instanceof Error ? error.message : String(error) }
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
if (payload.scope === 'all-frames') {
|
|
2040
|
+
emit({ ok: true, result: { scope: payload.scope, results } });
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
emit({ ok: true, result: { scope: payload.scope || 'current', result: results[0] } });
|
|
2044
|
+
})
|
|
2045
|
+
.catch((error) => {
|
|
2046
|
+
emit({
|
|
2047
|
+
ok: false,
|
|
2048
|
+
error: typeof error === 'object' && error !== null && 'code' in error
|
|
2049
|
+
? error
|
|
2050
|
+
: { code: 'E_EXECUTION', message: error instanceof Error ? error.message : String(error) }
|
|
2051
|
+
});
|
|
2052
|
+
});
|
|
2053
|
+
})();
|
|
2054
|
+
`;
|
|
2055
|
+
(document.documentElement ?? document.head ?? document.body).appendChild(injector);
|
|
2056
|
+
injector.remove();
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
function resolveScope(params) {
|
|
2060
|
+
const scope = typeof params.scope === "string" ? params.scope : "current";
|
|
2061
|
+
return scope === "main" || scope === "all-frames" ? scope : "current";
|
|
2062
|
+
}
|
|
2063
|
+
function pageWorldFramePath() {
|
|
2064
|
+
return [...contextState.framePath];
|
|
2065
|
+
}
|
|
2066
|
+
function pageEval(expr, params) {
|
|
2067
|
+
return runPageWorldRequest("eval", {
|
|
2068
|
+
expr,
|
|
2069
|
+
scope: resolveScope(params),
|
|
2070
|
+
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2071
|
+
framePath: pageWorldFramePath()
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
function pageExtract(path, params) {
|
|
2075
|
+
return runPageWorldRequest("extract", {
|
|
2076
|
+
path,
|
|
2077
|
+
scope: resolveScope(params),
|
|
2078
|
+
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2079
|
+
framePath: pageWorldFramePath()
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
function pageFetch(params) {
|
|
2083
|
+
return runPageWorldRequest("fetch", {
|
|
2084
|
+
url: String(params.url ?? ""),
|
|
2085
|
+
method: typeof params.method === "string" ? params.method : "GET",
|
|
2086
|
+
headers: typeof params.headers === "object" && params.headers !== null ? params.headers : void 0,
|
|
2087
|
+
body: typeof params.body === "string" ? params.body : void 0,
|
|
2088
|
+
contentType: typeof params.contentType === "string" ? params.contentType : void 0,
|
|
2089
|
+
mode: params.mode === "json" ? "json" : "raw",
|
|
2090
|
+
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2091
|
+
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : void 0,
|
|
2092
|
+
scope: resolveScope(params),
|
|
2093
|
+
framePath: pageWorldFramePath()
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
async function globalsPreview() {
|
|
2097
|
+
return Object.keys(window).filter((key) => /(data|table|json|state|store)/i.test(key)).slice(0, 50);
|
|
2098
|
+
}
|
|
2099
|
+
async function collectInspectionState(params = {}) {
|
|
2100
|
+
const rootResult = resolveRootForLocator();
|
|
2101
|
+
if (!rootResult.ok) {
|
|
2102
|
+
throw rootResult.error;
|
|
2103
|
+
}
|
|
2104
|
+
const root = rootResult.root;
|
|
2105
|
+
const metadata = documentMetadata(root, document);
|
|
2106
|
+
const visibleText = pageTextChunks(root, 40, 320);
|
|
2107
|
+
const combinedText = visibleText.map((chunk) => chunk.text).join("\n");
|
|
2108
|
+
const scripts = listInlineScripts();
|
|
2109
|
+
const visibleTimestamps = timestampCandidatesFromText(combinedText, Array.isArray(params.patterns) ? params.patterns.map(String) : void 0);
|
|
2110
|
+
const inlineTimestamps = timestampCandidatesFromText(
|
|
2111
|
+
scripts.map((script) => script.content).join("\n"),
|
|
2112
|
+
Array.isArray(params.patterns) ? params.patterns.map(String) : void 0
|
|
2113
|
+
);
|
|
2114
|
+
return {
|
|
2115
|
+
url: metadata.url,
|
|
2116
|
+
title: metadata.title,
|
|
2117
|
+
html: redactHtmlSnapshot(document.documentElement),
|
|
2118
|
+
visibleText,
|
|
2119
|
+
visibleTimestamps,
|
|
2120
|
+
inlineTimestamps,
|
|
2121
|
+
suspiciousGlobals: [...new Set(scripts.flatMap((script) => script.suspectedVars))].slice(0, 50),
|
|
2122
|
+
globalsPreview: await globalsPreview(),
|
|
2123
|
+
scripts: {
|
|
2124
|
+
inlineCount: scripts.length,
|
|
2125
|
+
suspectedDataVars: [...new Set(scripts.flatMap((script) => script.suspectedVars))].slice(0, 50)
|
|
2126
|
+
},
|
|
2127
|
+
storage: storageMetadata(),
|
|
2128
|
+
cookies: cookieMetadata(),
|
|
2129
|
+
frames: collectFrames(),
|
|
2130
|
+
tables: describeTables(),
|
|
2131
|
+
timers: {
|
|
2132
|
+
timeouts: 0,
|
|
2133
|
+
intervals: 0
|
|
2134
|
+
},
|
|
2135
|
+
pageLoadedAt,
|
|
2136
|
+
lastMutationAt,
|
|
2137
|
+
context: currentContextSnapshot()
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
1403
2140
|
function performDoubleClick(target, point) {
|
|
1404
2141
|
performClick(target, point);
|
|
1405
2142
|
performClick(target, point);
|
|
@@ -1667,6 +2404,12 @@
|
|
|
1667
2404
|
)
|
|
1668
2405
|
};
|
|
1669
2406
|
}
|
|
2407
|
+
case "page.eval":
|
|
2408
|
+
return await pageEval(String(params.expr ?? ""), params);
|
|
2409
|
+
case "page.extract":
|
|
2410
|
+
return await pageExtract(String(params.path ?? ""), params);
|
|
2411
|
+
case "page.fetch":
|
|
2412
|
+
return await pageFetch(params);
|
|
1670
2413
|
case "page.dom": {
|
|
1671
2414
|
const rootResult = resolveRootForLocator();
|
|
1672
2415
|
if (!rootResult.ok) {
|
|
@@ -1818,6 +2561,7 @@
|
|
|
1818
2561
|
if (!target) {
|
|
1819
2562
|
throw notFoundForLocator(params.locator, "element.get target not found");
|
|
1820
2563
|
}
|
|
2564
|
+
const matches = matchedElementsForLocator(params.locator);
|
|
1821
2565
|
const elements = collectElements({}, params.locator);
|
|
1822
2566
|
const eid = buildEid(target);
|
|
1823
2567
|
const element = elements.find((item) => item.eid === eid);
|
|
@@ -1830,6 +2574,10 @@
|
|
|
1830
2574
|
}
|
|
1831
2575
|
return {
|
|
1832
2576
|
element,
|
|
2577
|
+
matchedCount: matches.length,
|
|
2578
|
+
visible: isElementVisible(target),
|
|
2579
|
+
enabled: !(target.disabled || target.getAttribute("aria-disabled") === "true"),
|
|
2580
|
+
textPreview: (target.innerText || target.textContent || "").replace(/\s+/g, " ").trim().slice(0, 160),
|
|
1833
2581
|
value: isEditable(target) ? target.value : target.isContentEditable ? target.textContent ?? "" : void 0,
|
|
1834
2582
|
checked: isInputElement(target) ? target.checked : void 0,
|
|
1835
2583
|
attributes
|
|
@@ -1962,6 +2710,35 @@
|
|
|
1962
2710
|
dispatchInputEvents(target);
|
|
1963
2711
|
return { ok: true, fileCount: transfer.files.length };
|
|
1964
2712
|
}
|
|
2713
|
+
case "context.get":
|
|
2714
|
+
return {
|
|
2715
|
+
ok: true,
|
|
2716
|
+
frameDepth: contextState.framePath.length,
|
|
2717
|
+
shadowDepth: contextState.shadowPath.length,
|
|
2718
|
+
framePath: [...contextState.framePath],
|
|
2719
|
+
shadowPath: [...contextState.shadowPath]
|
|
2720
|
+
};
|
|
2721
|
+
case "context.set": {
|
|
2722
|
+
const framePath = Array.isArray(params.framePath) ? params.framePath.map(String) : [];
|
|
2723
|
+
const shadowPath = Array.isArray(params.shadowPath) ? params.shadowPath.map(String) : [];
|
|
2724
|
+
const frameResult = resolveFrameDocument(framePath);
|
|
2725
|
+
if (!frameResult.ok) {
|
|
2726
|
+
throw frameResult.error;
|
|
2727
|
+
}
|
|
2728
|
+
const shadowResult = resolveShadowRoot(frameResult.document, shadowPath);
|
|
2729
|
+
if (!shadowResult.ok) {
|
|
2730
|
+
throw shadowResult.error;
|
|
2731
|
+
}
|
|
2732
|
+
contextState.framePath = framePath;
|
|
2733
|
+
contextState.shadowPath = shadowPath;
|
|
2734
|
+
return {
|
|
2735
|
+
ok: true,
|
|
2736
|
+
frameDepth: contextState.framePath.length,
|
|
2737
|
+
shadowDepth: contextState.shadowPath.length,
|
|
2738
|
+
framePath: [...contextState.framePath],
|
|
2739
|
+
shadowPath: [...contextState.shadowPath]
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
1965
2742
|
case "context.enterFrame": {
|
|
1966
2743
|
if (params.reset === true) {
|
|
1967
2744
|
contextState.framePath = [];
|
|
@@ -2034,7 +2811,7 @@
|
|
|
2034
2811
|
if (!found) {
|
|
2035
2812
|
throw { code: "E_NOT_FOUND", message: `network entry not found: ${id}` };
|
|
2036
2813
|
}
|
|
2037
|
-
return { entry: found };
|
|
2814
|
+
return { entry: filterNetworkEntrySections(found, params.include) };
|
|
2038
2815
|
}
|
|
2039
2816
|
case "network.waitFor":
|
|
2040
2817
|
return { entry: await waitForNetwork(params) };
|
|
@@ -2042,6 +2819,34 @@
|
|
|
2042
2819
|
networkEntries.length = 0;
|
|
2043
2820
|
performanceBaselineMs = performance.now();
|
|
2044
2821
|
return { ok: true };
|
|
2822
|
+
case "table.list":
|
|
2823
|
+
return { tables: describeTables() };
|
|
2824
|
+
case "table.schema": {
|
|
2825
|
+
const resolved = resolveTable(String(params.table ?? ""));
|
|
2826
|
+
if (!resolved || !(resolved.element instanceof Element)) {
|
|
2827
|
+
throw { code: "E_NOT_FOUND", message: `table not found: ${String(params.table ?? "")}` };
|
|
2828
|
+
}
|
|
2829
|
+
const schema = resolved.element instanceof HTMLTableElement ? { columns: htmlTableSchema(resolved.element) } : { columns: gridSchema(resolved.element) };
|
|
2830
|
+
return {
|
|
2831
|
+
table: resolved.table,
|
|
2832
|
+
schema
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
case "table.rows":
|
|
2836
|
+
case "table.export": {
|
|
2837
|
+
const resolved = resolveTable(String(params.table ?? ""));
|
|
2838
|
+
if (!resolved || !(resolved.element instanceof Element)) {
|
|
2839
|
+
throw { code: "E_NOT_FOUND", message: `table not found: ${String(params.table ?? "")}` };
|
|
2840
|
+
}
|
|
2841
|
+
const requestedLimit = params.all === true ? typeof params.maxRows === "number" ? Math.max(1, Math.floor(params.maxRows)) : 1e4 : typeof params.limit === "number" ? Math.max(1, Math.floor(params.limit)) : 100;
|
|
2842
|
+
const extractionMode = resolved.table.kind === "html" || resolved.table.kind === "dataTables" ? "dataSource" : "visibleOnly";
|
|
2843
|
+
const rows = resolved.element instanceof HTMLTableElement ? htmlTableRows(resolved.element, requestedLimit) : gridRows(resolved.element, requestedLimit);
|
|
2844
|
+
return {
|
|
2845
|
+
table: resolved.table,
|
|
2846
|
+
extractionMode,
|
|
2847
|
+
rows
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2045
2850
|
case "debug.dumpState": {
|
|
2046
2851
|
const consoleLimit = typeof params.consoleLimit === "number" ? Math.max(1, Math.floor(params.consoleLimit)) : 80;
|
|
2047
2852
|
const networkLimit = typeof params.networkLimit === "number" ? Math.max(1, Math.floor(params.networkLimit)) : 80;
|
|
@@ -2051,6 +2856,7 @@
|
|
|
2051
2856
|
throw rootResult.error;
|
|
2052
2857
|
}
|
|
2053
2858
|
const metadata = documentMetadata(rootResult.root, document);
|
|
2859
|
+
const inspection = await collectInspectionState(params);
|
|
2054
2860
|
return {
|
|
2055
2861
|
url: metadata.url,
|
|
2056
2862
|
title: metadata.title,
|
|
@@ -2069,9 +2875,19 @@
|
|
|
2069
2875
|
},
|
|
2070
2876
|
console: consoleEntries.slice(-consoleLimit),
|
|
2071
2877
|
network: filterNetworkEntries({ limit: networkLimit }),
|
|
2072
|
-
accessibility: includeAccessibility ? pageAccessibility(rootResult.root, 200) : void 0
|
|
2878
|
+
accessibility: includeAccessibility ? pageAccessibility(rootResult.root, 200) : void 0,
|
|
2879
|
+
scripts: inspection.scripts,
|
|
2880
|
+
globalsPreview: inspection.globalsPreview,
|
|
2881
|
+
storage: inspection.storage,
|
|
2882
|
+
frames: inspection.frames,
|
|
2883
|
+
networkSummary: {
|
|
2884
|
+
total: filterNetworkEntries({ limit: networkLimit }).length,
|
|
2885
|
+
recent: filterNetworkEntries({ limit: Math.min(networkLimit, 10) })
|
|
2886
|
+
}
|
|
2073
2887
|
};
|
|
2074
2888
|
}
|
|
2889
|
+
case "bak.internal.inspectState":
|
|
2890
|
+
return await collectInspectionState(params);
|
|
2075
2891
|
default:
|
|
2076
2892
|
throw { code: "E_NOT_FOUND", message: `Unsupported content RPC method: ${method}` };
|
|
2077
2893
|
}
|
|
@@ -2124,4 +2940,5 @@
|
|
|
2124
2940
|
return false;
|
|
2125
2941
|
});
|
|
2126
2942
|
ensureOverlayRoot();
|
|
2943
|
+
trackMutations();
|
|
2127
2944
|
})();
|