@flrande/bak-extension 0.5.0 → 0.6.1
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 +1795 -262
- package/dist/content.global.js +852 -3
- package/dist/manifest.json +2 -2
- package/package.json +2 -2
- package/public/manifest.json +2 -2
- package/src/background.ts +1544 -269
- package/src/content.ts +996 -130
- package/src/network-debugger.ts +495 -0
- package/src/privacy.ts +112 -1
- package/src/session-binding-storage.ts +50 -68
- package/src/{workspace.ts → session-binding.ts} +228 -213
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,9 @@
|
|
|
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();
|
|
239
|
+
var DISCOVERY_GLOBAL_PATTERN = /(data|table|json|state|store|market|quote|flow|row|timestamp|snapshot|book|signal)/i;
|
|
166
240
|
function isHtmlElement(node) {
|
|
167
241
|
if (!node) {
|
|
168
242
|
return false;
|
|
@@ -710,6 +784,28 @@
|
|
|
710
784
|
}
|
|
711
785
|
return collected;
|
|
712
786
|
}
|
|
787
|
+
function evaluateXPath(root, expression) {
|
|
788
|
+
try {
|
|
789
|
+
const ownerDocument = isDocumentNode(root) ? root : root.ownerDocument ?? document;
|
|
790
|
+
const iterator = ownerDocument.evaluate(
|
|
791
|
+
expression,
|
|
792
|
+
root,
|
|
793
|
+
null,
|
|
794
|
+
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
|
795
|
+
null
|
|
796
|
+
);
|
|
797
|
+
const matches = [];
|
|
798
|
+
for (let index = 0; index < iterator.snapshotLength; index += 1) {
|
|
799
|
+
const node = iterator.snapshotItem(index);
|
|
800
|
+
if (isHtmlElement(node)) {
|
|
801
|
+
matches.push(node);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return matches;
|
|
805
|
+
} catch {
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
}
|
|
713
809
|
function resolveFrameDocument(framePath) {
|
|
714
810
|
let currentDocument = document;
|
|
715
811
|
for (const selector of framePath) {
|
|
@@ -782,6 +878,7 @@
|
|
|
782
878
|
const eid = buildEid(element);
|
|
783
879
|
const selectors = {
|
|
784
880
|
css: toCssSelector(element),
|
|
881
|
+
xpath: null,
|
|
785
882
|
text: text || name ? (text || name).slice(0, 80) : null,
|
|
786
883
|
aria: role && name ? `${role}:${name.slice(0, 80)}` : null
|
|
787
884
|
};
|
|
@@ -793,6 +890,8 @@
|
|
|
793
890
|
role,
|
|
794
891
|
name,
|
|
795
892
|
text,
|
|
893
|
+
visible: isElementVisible(element),
|
|
894
|
+
enabled: !(element.disabled || element.getAttribute("aria-disabled") === "true"),
|
|
796
895
|
bbox: {
|
|
797
896
|
x: rect.x,
|
|
798
897
|
y: rect.y,
|
|
@@ -903,6 +1002,13 @@
|
|
|
903
1002
|
currentRoot = found.shadowRoot;
|
|
904
1003
|
}
|
|
905
1004
|
}
|
|
1005
|
+
if (locator.xpath) {
|
|
1006
|
+
const matches = evaluateXPath(root, locator.xpath).filter((item) => isElementVisible(item));
|
|
1007
|
+
const found = indexedCandidate(matches, locator);
|
|
1008
|
+
if (found) {
|
|
1009
|
+
return found;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
906
1012
|
if (locator.name) {
|
|
907
1013
|
const fallback = indexedCandidate(
|
|
908
1014
|
interactive.filter((element) => inferName(element).toLowerCase().includes(locator.name.toLowerCase())),
|
|
@@ -914,6 +1020,61 @@
|
|
|
914
1020
|
}
|
|
915
1021
|
return null;
|
|
916
1022
|
}
|
|
1023
|
+
function matchedElementsForLocator(locator) {
|
|
1024
|
+
if (!locator) {
|
|
1025
|
+
return [];
|
|
1026
|
+
}
|
|
1027
|
+
const rootResult = resolveRootForLocator(locator);
|
|
1028
|
+
if (!rootResult.ok) {
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
const root = rootResult.root;
|
|
1032
|
+
const interactive = getInteractiveElements(root, locator.shadow !== "none");
|
|
1033
|
+
if (locator.css) {
|
|
1034
|
+
const parts = splitShadowSelector(locator.css);
|
|
1035
|
+
let currentRoot = root;
|
|
1036
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
1037
|
+
const selector = parts[index];
|
|
1038
|
+
const found = locator.shadow === "none" ? querySelectorInTree(currentRoot, selector) : querySelectorAcrossOpenShadow(currentRoot, selector);
|
|
1039
|
+
if (!found) {
|
|
1040
|
+
return [];
|
|
1041
|
+
}
|
|
1042
|
+
if (index === parts.length - 1) {
|
|
1043
|
+
return (locator.shadow === "none" ? Array.from(currentRoot.querySelectorAll(selector)) : querySelectorAllAcrossOpenShadow(currentRoot, selector)).filter((item) => isElementVisible(item));
|
|
1044
|
+
}
|
|
1045
|
+
if (!found.shadowRoot) {
|
|
1046
|
+
return [];
|
|
1047
|
+
}
|
|
1048
|
+
currentRoot = found.shadowRoot;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (locator.xpath) {
|
|
1052
|
+
return evaluateXPath(root, locator.xpath).filter((item) => isElementVisible(item));
|
|
1053
|
+
}
|
|
1054
|
+
if (locator.role || locator.name || locator.text) {
|
|
1055
|
+
return interactive.filter((element) => {
|
|
1056
|
+
if (locator.role && inferRole(element).toLowerCase() !== locator.role.toLowerCase()) {
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
if (locator.name && !inferName(element).toLowerCase().includes(locator.name.toLowerCase())) {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
if (locator.text) {
|
|
1063
|
+
const text = (element.innerText || element.textContent || "").toLowerCase();
|
|
1064
|
+
if (!text.includes(locator.text.toLowerCase())) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return true;
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
if (locator.eid) {
|
|
1072
|
+
collectElements({}, locator);
|
|
1073
|
+
const fromCache = elementCache.get(locator.eid);
|
|
1074
|
+
return fromCache ? [fromCache] : [];
|
|
1075
|
+
}
|
|
1076
|
+
return [];
|
|
1077
|
+
}
|
|
917
1078
|
function ensureOverlayRoot() {
|
|
918
1079
|
let root = document.getElementById("bak-overlay-root");
|
|
919
1080
|
if (!root) {
|
|
@@ -1387,6 +1548,30 @@
|
|
|
1387
1548
|
return true;
|
|
1388
1549
|
}).slice(-limit).reverse();
|
|
1389
1550
|
}
|
|
1551
|
+
function filterNetworkEntrySections(entry, include) {
|
|
1552
|
+
if (!Array.isArray(include)) {
|
|
1553
|
+
return entry;
|
|
1554
|
+
}
|
|
1555
|
+
const sections = new Set(
|
|
1556
|
+
include.map(String).filter((section) => section === "request" || section === "response")
|
|
1557
|
+
);
|
|
1558
|
+
if (sections.size === 0 || sections.size === 2) {
|
|
1559
|
+
return entry;
|
|
1560
|
+
}
|
|
1561
|
+
const clone = { ...entry };
|
|
1562
|
+
if (!sections.has("request")) {
|
|
1563
|
+
delete clone.requestHeaders;
|
|
1564
|
+
delete clone.requestBodyPreview;
|
|
1565
|
+
delete clone.requestBodyTruncated;
|
|
1566
|
+
}
|
|
1567
|
+
if (!sections.has("response")) {
|
|
1568
|
+
delete clone.responseHeaders;
|
|
1569
|
+
delete clone.responseBodyPreview;
|
|
1570
|
+
delete clone.responseBodyTruncated;
|
|
1571
|
+
delete clone.binary;
|
|
1572
|
+
}
|
|
1573
|
+
return clone;
|
|
1574
|
+
}
|
|
1390
1575
|
async function waitForNetwork(params) {
|
|
1391
1576
|
const timeoutMs = typeof params.timeoutMs === "number" ? Math.max(1, params.timeoutMs) : 5e3;
|
|
1392
1577
|
const sinceTs = typeof params.sinceTs === "number" ? params.sinceTs : Date.now() - timeoutMs;
|
|
@@ -1400,6 +1585,619 @@
|
|
|
1400
1585
|
}
|
|
1401
1586
|
throw { code: "E_TIMEOUT", message: "network.waitFor timeout" };
|
|
1402
1587
|
}
|
|
1588
|
+
function trackMutations() {
|
|
1589
|
+
const observer = new MutationObserver(() => {
|
|
1590
|
+
lastMutationAt = Date.now();
|
|
1591
|
+
});
|
|
1592
|
+
const root = document.documentElement ?? document.body;
|
|
1593
|
+
if (!root) {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
observer.observe(root, {
|
|
1597
|
+
childList: true,
|
|
1598
|
+
subtree: true,
|
|
1599
|
+
attributes: true,
|
|
1600
|
+
characterData: true
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
function currentContextSnapshot() {
|
|
1604
|
+
return {
|
|
1605
|
+
tabId: null,
|
|
1606
|
+
framePath: [...contextState.framePath],
|
|
1607
|
+
shadowPath: [...contextState.shadowPath]
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function timestampCandidateMatchesFromText(text, patterns) {
|
|
1611
|
+
const regexes = (patterns ?? [
|
|
1612
|
+
String.raw`\b20\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b`,
|
|
1613
|
+
String.raw`\b20\d{2}-\d{2}-\d{2}\b`,
|
|
1614
|
+
String.raw`\b20\d{2}\/\d{2}\/\d{2}\b`
|
|
1615
|
+
]).map((pattern) => new RegExp(pattern, "gi"));
|
|
1616
|
+
const collected = /* @__PURE__ */ new Map();
|
|
1617
|
+
for (const regex of regexes) {
|
|
1618
|
+
for (const match of text.matchAll(regex)) {
|
|
1619
|
+
const value = match[0];
|
|
1620
|
+
if (!value) {
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
const index = match.index ?? text.indexOf(value);
|
|
1624
|
+
const start = Math.max(0, index - 28);
|
|
1625
|
+
const end = Math.min(text.length, index + value.length + 28);
|
|
1626
|
+
const context = text.slice(start, end).replace(/\s+/g, " ").trim();
|
|
1627
|
+
const key = `${value}::${context}`;
|
|
1628
|
+
if (!collected.has(key)) {
|
|
1629
|
+
collected.set(key, { value, context });
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
return [...collected.values()];
|
|
1634
|
+
}
|
|
1635
|
+
function listInlineScripts() {
|
|
1636
|
+
return Array.from(document.scripts).filter((script) => !script.src).map((script) => {
|
|
1637
|
+
const content = script.textContent ?? "";
|
|
1638
|
+
const suspectedVars = [...content.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/g)].map((match) => match[1] ?? "").filter((candidate) => DISCOVERY_GLOBAL_PATTERN.test(candidate));
|
|
1639
|
+
return { content, suspectedVars };
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
function cookieMetadata() {
|
|
1643
|
+
return document.cookie.split(";").map((item) => item.trim()).filter(Boolean).map((item) => ({ name: item.split("=")[0] ?? "" })).filter((item) => item.name.length > 0);
|
|
1644
|
+
}
|
|
1645
|
+
function storageMetadata() {
|
|
1646
|
+
return {
|
|
1647
|
+
localStorageKeys: Object.keys(localStorage),
|
|
1648
|
+
sessionStorageKeys: Object.keys(sessionStorage)
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function collectFrames(baseDocument = document, framePath = []) {
|
|
1652
|
+
const frames = [];
|
|
1653
|
+
const frameNodes = Array.from(baseDocument.querySelectorAll("iframe,frame"));
|
|
1654
|
+
for (const frame of frameNodes) {
|
|
1655
|
+
if (!isFrameElement(frame)) {
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
const selector = frame.id ? `#${frame.id}` : `${frame.tagName.toLowerCase()}:nth-of-type(${frameNodes.indexOf(frame) + 1})`;
|
|
1659
|
+
try {
|
|
1660
|
+
const childDocument = frame.contentDocument;
|
|
1661
|
+
if (!childDocument) {
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
const nextPath = [...framePath, selector];
|
|
1665
|
+
frames.push({
|
|
1666
|
+
framePath: nextPath,
|
|
1667
|
+
url: childDocument.location.href
|
|
1668
|
+
});
|
|
1669
|
+
frames.push(...collectFrames(childDocument, nextPath));
|
|
1670
|
+
} catch {
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return frames;
|
|
1675
|
+
}
|
|
1676
|
+
function buildTableId(kind, index) {
|
|
1677
|
+
return `${kind}:${index + 1}`;
|
|
1678
|
+
}
|
|
1679
|
+
function describeTables() {
|
|
1680
|
+
const rootResult = resolveRootForLocator();
|
|
1681
|
+
if (!rootResult.ok) {
|
|
1682
|
+
return [];
|
|
1683
|
+
}
|
|
1684
|
+
const root = rootResult.root;
|
|
1685
|
+
const tables = [];
|
|
1686
|
+
const htmlTables = Array.from(root.querySelectorAll("table"));
|
|
1687
|
+
for (const [index, table] of htmlTables.entries()) {
|
|
1688
|
+
tables.push({
|
|
1689
|
+
id: buildTableId(table.closest(".dataTables_wrapper") ? "dataTables" : "html", index),
|
|
1690
|
+
name: (table.getAttribute("aria-label") || table.getAttribute("data-testid") || table.id || `table-${index + 1}`).trim(),
|
|
1691
|
+
kind: table.closest(".dataTables_wrapper") ? "dataTables" : "html",
|
|
1692
|
+
selector: table.id ? `#${table.id}` : void 0,
|
|
1693
|
+
rowCount: table.querySelectorAll("tbody tr").length || table.querySelectorAll("tr").length,
|
|
1694
|
+
columnCount: table.querySelectorAll("thead th").length || table.querySelectorAll("tr:first-child th, tr:first-child td").length
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
const gridRoots = Array.from(root.querySelectorAll('[role="grid"], [role="table"], .ag-root, .ag-root-wrapper'));
|
|
1698
|
+
for (const [index, grid] of gridRoots.entries()) {
|
|
1699
|
+
const kind = grid.className.includes("ag-") ? "ag-grid" : "aria-grid";
|
|
1700
|
+
tables.push({
|
|
1701
|
+
id: buildTableId(kind, index),
|
|
1702
|
+
name: (grid.getAttribute("aria-label") || grid.getAttribute("data-testid") || grid.id || `grid-${index + 1}`).trim(),
|
|
1703
|
+
kind,
|
|
1704
|
+
selector: grid.id ? `#${grid.id}` : void 0,
|
|
1705
|
+
rowCount: grid.querySelectorAll('[role="row"]').length,
|
|
1706
|
+
columnCount: grid.querySelectorAll('[role="columnheader"]').length
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
return tables;
|
|
1710
|
+
}
|
|
1711
|
+
function resolveTable(handleId) {
|
|
1712
|
+
const tables = describeTables();
|
|
1713
|
+
const handle = tables.find((candidate) => candidate.id === handleId);
|
|
1714
|
+
if (!handle) {
|
|
1715
|
+
return null;
|
|
1716
|
+
}
|
|
1717
|
+
const rootResult = resolveRootForLocator();
|
|
1718
|
+
if (!rootResult.ok) {
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1721
|
+
const root = rootResult.root;
|
|
1722
|
+
if (!handle.selector) {
|
|
1723
|
+
if (handle.kind === "html" || handle.kind === "dataTables") {
|
|
1724
|
+
const index2 = Number(handle.id.split(":")[1] ?? "1") - 1;
|
|
1725
|
+
return { table: handle, element: root.querySelectorAll("table")[index2] ?? null };
|
|
1726
|
+
}
|
|
1727
|
+
const candidates = root.querySelectorAll('[role="grid"], [role="table"], .ag-root, .ag-root-wrapper');
|
|
1728
|
+
const index = Number(handle.id.split(":")[1] ?? "1") - 1;
|
|
1729
|
+
return { table: handle, element: candidates[index] ?? null };
|
|
1730
|
+
}
|
|
1731
|
+
return { table: handle, element: root.querySelector(handle.selector) };
|
|
1732
|
+
}
|
|
1733
|
+
function htmlTableSchema(table) {
|
|
1734
|
+
const headers = Array.from(table.querySelectorAll("thead th"));
|
|
1735
|
+
if (headers.length > 0) {
|
|
1736
|
+
return headers.map((cell, index) => ({
|
|
1737
|
+
key: `col_${index + 1}`,
|
|
1738
|
+
label: (cell.textContent ?? "").trim() || `Column ${index + 1}`
|
|
1739
|
+
}));
|
|
1740
|
+
}
|
|
1741
|
+
const firstRow = table.querySelector("tr");
|
|
1742
|
+
return Array.from(firstRow?.querySelectorAll("th,td") ?? []).map((cell, index) => ({
|
|
1743
|
+
key: `col_${index + 1}`,
|
|
1744
|
+
label: (cell.textContent ?? "").trim() || `Column ${index + 1}`
|
|
1745
|
+
}));
|
|
1746
|
+
}
|
|
1747
|
+
function htmlTableRows(table, limit) {
|
|
1748
|
+
const columns = htmlTableSchema(table);
|
|
1749
|
+
const rowNodes = Array.from(table.querySelectorAll("tbody tr")).length > 0 ? Array.from(table.querySelectorAll("tbody tr")) : Array.from(table.querySelectorAll("tr")).slice(1);
|
|
1750
|
+
return rowNodes.slice(0, limit).map((row) => {
|
|
1751
|
+
const record = {};
|
|
1752
|
+
Array.from(row.querySelectorAll("th,td")).forEach((cell, index) => {
|
|
1753
|
+
const key = columns[index]?.label ?? `Column ${index + 1}`;
|
|
1754
|
+
record[key] = (cell.textContent ?? "").trim();
|
|
1755
|
+
});
|
|
1756
|
+
return record;
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
function gridSchema(grid) {
|
|
1760
|
+
return Array.from(grid.querySelectorAll('[role="columnheader"]')).map((cell, index) => ({
|
|
1761
|
+
key: `col_${index + 1}`,
|
|
1762
|
+
label: (cell.textContent ?? "").trim() || `Column ${index + 1}`
|
|
1763
|
+
}));
|
|
1764
|
+
}
|
|
1765
|
+
function gridRows(grid, limit) {
|
|
1766
|
+
const columns = gridSchema(grid);
|
|
1767
|
+
return Array.from(grid.querySelectorAll('[role="row"]')).slice(0, limit).map((row) => {
|
|
1768
|
+
const record = {};
|
|
1769
|
+
Array.from(row.querySelectorAll('[role="gridcell"], [role="cell"], [role="columnheader"]')).forEach((cell, index) => {
|
|
1770
|
+
const key = columns[index]?.label ?? `Column ${index + 1}`;
|
|
1771
|
+
record[key] = (cell.textContent ?? "").trim();
|
|
1772
|
+
});
|
|
1773
|
+
return record;
|
|
1774
|
+
}).filter((row) => Object.values(row).some((value) => String(value).trim().length > 0));
|
|
1775
|
+
}
|
|
1776
|
+
function runPageWorldRequest(action, payload) {
|
|
1777
|
+
const requestId = `bak_page_world_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
1778
|
+
return new Promise((resolve, reject) => {
|
|
1779
|
+
const timeoutMs = typeof payload.timeoutMs === "number" && Number.isFinite(payload.timeoutMs) && payload.timeoutMs > 0 ? Math.max(1e3, Math.floor(payload.timeoutMs) + 1e3) : 15e3;
|
|
1780
|
+
const responseNode = document.createElement("div");
|
|
1781
|
+
responseNode.setAttribute("data-bak-page-world-response", requestId);
|
|
1782
|
+
responseNode.hidden = true;
|
|
1783
|
+
(document.documentElement ?? document.head ?? document.body).appendChild(responseNode);
|
|
1784
|
+
const cleanup = () => {
|
|
1785
|
+
clearTimeout(timer);
|
|
1786
|
+
observer.disconnect();
|
|
1787
|
+
responseNode.remove();
|
|
1788
|
+
};
|
|
1789
|
+
const tryResolveFromNode = () => {
|
|
1790
|
+
if (responseNode.getAttribute("data-ready") !== "1") {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const payloadText = responseNode.getAttribute("data-payload");
|
|
1794
|
+
const detail = payloadText ? JSON.parse(payloadText) : null;
|
|
1795
|
+
if (!detail) {
|
|
1796
|
+
cleanup();
|
|
1797
|
+
reject(failAction("E_EXECUTION", `${action} returned no detail`));
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
cleanup();
|
|
1801
|
+
if (detail.ok) {
|
|
1802
|
+
resolve(detail.result);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
reject(detail.error ?? failAction("E_EXECUTION", `${action} failed`));
|
|
1806
|
+
};
|
|
1807
|
+
const observer = new MutationObserver(() => {
|
|
1808
|
+
try {
|
|
1809
|
+
tryResolveFromNode();
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
cleanup();
|
|
1812
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
observer.observe(responseNode, {
|
|
1816
|
+
attributes: true,
|
|
1817
|
+
childList: true,
|
|
1818
|
+
characterData: true,
|
|
1819
|
+
subtree: true
|
|
1820
|
+
});
|
|
1821
|
+
const timer = window.setTimeout(() => {
|
|
1822
|
+
cleanup();
|
|
1823
|
+
reject(failAction("E_TIMEOUT", `${action} timed out`));
|
|
1824
|
+
}, timeoutMs);
|
|
1825
|
+
const injector = document.createElement("script");
|
|
1826
|
+
injector.textContent = `
|
|
1827
|
+
(() => {
|
|
1828
|
+
const requestId = ${JSON.stringify(requestId)};
|
|
1829
|
+
const action = ${JSON.stringify(action)};
|
|
1830
|
+
const payload = ${JSON.stringify(payload)};
|
|
1831
|
+
const responseNode = document.querySelector('div[data-bak-page-world-response="' + requestId + '"]');
|
|
1832
|
+
const emit = (detail) => {
|
|
1833
|
+
if (!responseNode) {
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
responseNode.setAttribute('data-payload', JSON.stringify(detail));
|
|
1837
|
+
responseNode.setAttribute('data-ready', '1');
|
|
1838
|
+
};
|
|
1839
|
+
const serializeValue = (value, maxBytes) => {
|
|
1840
|
+
let cloned;
|
|
1841
|
+
try {
|
|
1842
|
+
cloned = typeof structuredClone === 'function' ? structuredClone(value) : JSON.parse(JSON.stringify(value));
|
|
1843
|
+
} catch (error) {
|
|
1844
|
+
throw { code: 'E_NOT_SERIALIZABLE', message: error instanceof Error ? error.message : String(error) };
|
|
1845
|
+
}
|
|
1846
|
+
const json = JSON.stringify(cloned);
|
|
1847
|
+
if (typeof maxBytes === 'number' && maxBytes > 0 && json.length > maxBytes) {
|
|
1848
|
+
throw { code: 'E_BODY_TOO_LARGE', message: 'serialized value exceeds max-bytes', details: { bytes: json.length, maxBytes } };
|
|
1849
|
+
}
|
|
1850
|
+
return { value: cloned, bytes: json.length };
|
|
1851
|
+
};
|
|
1852
|
+
const resolveFrameWindow = (framePath) => {
|
|
1853
|
+
let currentWindow = window;
|
|
1854
|
+
let currentDocument = document;
|
|
1855
|
+
for (const selector of framePath || []) {
|
|
1856
|
+
const frame = currentDocument.querySelector(selector);
|
|
1857
|
+
if (!frame || !('contentWindow' in frame)) {
|
|
1858
|
+
throw { code: 'E_NOT_FOUND', message: 'frame not found: ' + selector };
|
|
1859
|
+
}
|
|
1860
|
+
const nextWindow = frame.contentWindow;
|
|
1861
|
+
if (!nextWindow) {
|
|
1862
|
+
throw { code: 'E_NOT_READY', message: 'frame window unavailable: ' + selector };
|
|
1863
|
+
}
|
|
1864
|
+
try {
|
|
1865
|
+
currentDocument = nextWindow.document;
|
|
1866
|
+
} catch {
|
|
1867
|
+
throw { code: 'E_CROSS_ORIGIN_BLOCKED', message: 'cross-origin frame is not accessible: ' + selector };
|
|
1868
|
+
}
|
|
1869
|
+
currentWindow = nextWindow;
|
|
1870
|
+
}
|
|
1871
|
+
return currentWindow;
|
|
1872
|
+
};
|
|
1873
|
+
const collectFrames = (rootWindow, framePath) => {
|
|
1874
|
+
const collected = [{
|
|
1875
|
+
url: rootWindow.location.href,
|
|
1876
|
+
framePath,
|
|
1877
|
+
targetWindow: rootWindow
|
|
1878
|
+
}];
|
|
1879
|
+
const frames = Array.from(rootWindow.document.querySelectorAll('iframe,frame'));
|
|
1880
|
+
frames.forEach((frame, index) => {
|
|
1881
|
+
const selector = frame.id ? '#' + frame.id : frame.tagName.toLowerCase() + ':nth-of-type(' + (index + 1) + ')';
|
|
1882
|
+
try {
|
|
1883
|
+
if (!frame.contentWindow || !frame.contentWindow.document) {
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
collected.push(...collectFrames(frame.contentWindow, [...framePath, selector]));
|
|
1887
|
+
} catch {
|
|
1888
|
+
// Skip cross-origin frames.
|
|
1889
|
+
}
|
|
1890
|
+
});
|
|
1891
|
+
return collected;
|
|
1892
|
+
};
|
|
1893
|
+
const parsePath = (path) => {
|
|
1894
|
+
if (typeof path !== 'string' || !path.trim()) {
|
|
1895
|
+
throw { code: 'E_INVALID_PARAMS', message: 'path is required' };
|
|
1896
|
+
}
|
|
1897
|
+
const normalized = path.replace(/^globalThis\\.?/, '').replace(/^window\\.?/, '').trim();
|
|
1898
|
+
if (!normalized) {
|
|
1899
|
+
return [];
|
|
1900
|
+
}
|
|
1901
|
+
const segments = [];
|
|
1902
|
+
let index = 0;
|
|
1903
|
+
while (index < normalized.length) {
|
|
1904
|
+
if (normalized[index] === '.') {
|
|
1905
|
+
index += 1;
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
if (normalized[index] === '[') {
|
|
1909
|
+
const bracket = normalized.slice(index).match(/^\\[(\\d+)\\]/);
|
|
1910
|
+
if (!bracket) {
|
|
1911
|
+
throw { code: 'E_INVALID_PARAMS', message: 'Only numeric bracket paths are supported' };
|
|
1912
|
+
}
|
|
1913
|
+
segments.push(Number(bracket[1]));
|
|
1914
|
+
index += bracket[0].length;
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
const identifier = normalized.slice(index).match(/^[A-Za-z_$][\\w$]*/);
|
|
1918
|
+
if (!identifier) {
|
|
1919
|
+
throw { code: 'E_INVALID_PARAMS', message: 'Unsupported path token near: ' + normalized.slice(index, index + 16) };
|
|
1920
|
+
}
|
|
1921
|
+
segments.push(identifier[0]);
|
|
1922
|
+
index += identifier[0].length;
|
|
1923
|
+
}
|
|
1924
|
+
return segments;
|
|
1925
|
+
};
|
|
1926
|
+
const buildPathExpression = (path) => {
|
|
1927
|
+
const segments = parsePath(path);
|
|
1928
|
+
return segments
|
|
1929
|
+
.map((segment, index) => {
|
|
1930
|
+
if (typeof segment === 'number') {
|
|
1931
|
+
return '[' + segment + ']';
|
|
1932
|
+
}
|
|
1933
|
+
if (index === 0) {
|
|
1934
|
+
return segment;
|
|
1935
|
+
}
|
|
1936
|
+
return '.' + segment;
|
|
1937
|
+
})
|
|
1938
|
+
.join('');
|
|
1939
|
+
};
|
|
1940
|
+
const readPath = (targetWindow, path) => {
|
|
1941
|
+
const segments = parsePath(path);
|
|
1942
|
+
let current = targetWindow;
|
|
1943
|
+
for (const segment of segments) {
|
|
1944
|
+
if (current == null || !(segment in current)) {
|
|
1945
|
+
throw { code: 'E_NOT_FOUND', message: 'path not found: ' + path };
|
|
1946
|
+
}
|
|
1947
|
+
current = current[segment];
|
|
1948
|
+
}
|
|
1949
|
+
return current;
|
|
1950
|
+
};
|
|
1951
|
+
const resolveExtractValue = (targetWindow, path, resolver) => {
|
|
1952
|
+
const strategy = resolver === 'globalThis' || resolver === 'lexical' ? resolver : 'auto';
|
|
1953
|
+
const lexicalExpression = buildPathExpression(path);
|
|
1954
|
+
const readLexical = () => {
|
|
1955
|
+
try {
|
|
1956
|
+
return targetWindow.eval(lexicalExpression);
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
if (error instanceof ReferenceError) {
|
|
1959
|
+
throw { code: 'E_NOT_FOUND', message: 'path not found: ' + path };
|
|
1960
|
+
}
|
|
1961
|
+
throw error;
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
if (strategy === 'globalThis') {
|
|
1965
|
+
return { resolver: 'globalThis', value: readPath(targetWindow, path) };
|
|
1966
|
+
}
|
|
1967
|
+
if (strategy === 'lexical') {
|
|
1968
|
+
return { resolver: 'lexical', value: readLexical() };
|
|
1969
|
+
}
|
|
1970
|
+
try {
|
|
1971
|
+
return { resolver: 'globalThis', value: readPath(targetWindow, path) };
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
if (!error || typeof error !== 'object' || error.code !== 'E_NOT_FOUND') {
|
|
1974
|
+
throw error;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
return { resolver: 'lexical', value: readLexical() };
|
|
1978
|
+
};
|
|
1979
|
+
const buildScopeTargets = () => {
|
|
1980
|
+
if (payload.scope === 'main') {
|
|
1981
|
+
return [{
|
|
1982
|
+
url: window.location.href,
|
|
1983
|
+
framePath: [],
|
|
1984
|
+
targetWindow: window
|
|
1985
|
+
}];
|
|
1986
|
+
}
|
|
1987
|
+
if (payload.scope === 'all-frames') {
|
|
1988
|
+
return collectFrames(window, []);
|
|
1989
|
+
}
|
|
1990
|
+
return [{
|
|
1991
|
+
url: resolveFrameWindow(payload.framePath || []).location.href,
|
|
1992
|
+
framePath: payload.framePath || [],
|
|
1993
|
+
targetWindow: resolveFrameWindow(payload.framePath || [])
|
|
1994
|
+
}];
|
|
1995
|
+
};
|
|
1996
|
+
const toResult = async (target) => {
|
|
1997
|
+
if (action === 'globals') {
|
|
1998
|
+
const keys = Object.keys(target.targetWindow).filter((key) => ${DISCOVERY_GLOBAL_PATTERN}.test(key)).slice(0, 50);
|
|
1999
|
+
return { url: target.url, framePath: target.framePath, value: keys };
|
|
2000
|
+
}
|
|
2001
|
+
if (action === 'eval') {
|
|
2002
|
+
const serialized = serializeValue(target.targetWindow.eval(payload.expr), payload.maxBytes);
|
|
2003
|
+
return { url: target.url, framePath: target.framePath, value: serialized.value, bytes: serialized.bytes };
|
|
2004
|
+
}
|
|
2005
|
+
if (action === 'extract') {
|
|
2006
|
+
const extracted = resolveExtractValue(target.targetWindow, payload.path, payload.resolver);
|
|
2007
|
+
const serialized = serializeValue(extracted.value, payload.maxBytes);
|
|
2008
|
+
return { url: target.url, framePath: target.framePath, value: serialized.value, bytes: serialized.bytes, resolver: extracted.resolver };
|
|
2009
|
+
}
|
|
2010
|
+
if (action === 'fetch') {
|
|
2011
|
+
const headers = { ...(payload.headers || {}) };
|
|
2012
|
+
if (payload.contentType && !headers['Content-Type']) {
|
|
2013
|
+
headers['Content-Type'] = payload.contentType;
|
|
2014
|
+
}
|
|
2015
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
2016
|
+
const timeoutId =
|
|
2017
|
+
controller && typeof payload.timeoutMs === 'number' && payload.timeoutMs > 0
|
|
2018
|
+
? window.setTimeout(() => controller.abort(new Error('fetch timeout')), payload.timeoutMs)
|
|
2019
|
+
: null;
|
|
2020
|
+
let response;
|
|
2021
|
+
try {
|
|
2022
|
+
response = await target.targetWindow.fetch(payload.url, {
|
|
2023
|
+
method: payload.method || 'GET',
|
|
2024
|
+
headers,
|
|
2025
|
+
body: typeof payload.body === 'string' ? payload.body : undefined,
|
|
2026
|
+
signal: controller ? controller.signal : undefined
|
|
2027
|
+
});
|
|
2028
|
+
} finally {
|
|
2029
|
+
if (timeoutId !== null) {
|
|
2030
|
+
window.clearTimeout(timeoutId);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
const headerMap = {};
|
|
2034
|
+
response.headers.forEach((value, key) => {
|
|
2035
|
+
headerMap[key] = value;
|
|
2036
|
+
});
|
|
2037
|
+
const bodyText = await response.text();
|
|
2038
|
+
const encoder = typeof TextEncoder === 'function' ? new TextEncoder() : null;
|
|
2039
|
+
const decoder = typeof TextDecoder === 'function' ? new TextDecoder() : null;
|
|
2040
|
+
const previewLimit = typeof payload.maxBytes === 'number' && payload.maxBytes > 0 ? payload.maxBytes : 8192;
|
|
2041
|
+
const encodedBody = encoder ? encoder.encode(bodyText) : null;
|
|
2042
|
+
const bodyBytes = encodedBody ? encodedBody.byteLength : bodyText.length;
|
|
2043
|
+
const truncated = bodyBytes > previewLimit;
|
|
2044
|
+
const finalBodyText =
|
|
2045
|
+
encodedBody && decoder
|
|
2046
|
+
? decoder.decode(encodedBody.subarray(0, Math.min(encodedBody.byteLength, previewLimit)))
|
|
2047
|
+
: truncated
|
|
2048
|
+
? bodyText.slice(0, previewLimit)
|
|
2049
|
+
: bodyText;
|
|
2050
|
+
if (payload.mode === 'json' && truncated) {
|
|
2051
|
+
throw {
|
|
2052
|
+
code: 'E_BODY_TOO_LARGE',
|
|
2053
|
+
message: 'JSON response exceeds max-bytes',
|
|
2054
|
+
details: {
|
|
2055
|
+
bytes: bodyBytes,
|
|
2056
|
+
maxBytes: previewLimit
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
const result = {
|
|
2061
|
+
url: response.url,
|
|
2062
|
+
status: response.status,
|
|
2063
|
+
ok: response.ok,
|
|
2064
|
+
headers: headerMap,
|
|
2065
|
+
contentType: response.headers.get('content-type') || undefined,
|
|
2066
|
+
bodyText: payload.mode === 'json' ? undefined : finalBodyText,
|
|
2067
|
+
json: payload.mode === 'json' && bodyText ? JSON.parse(bodyText) : undefined,
|
|
2068
|
+
bytes: bodyBytes,
|
|
2069
|
+
truncated
|
|
2070
|
+
};
|
|
2071
|
+
return { url: target.url, framePath: target.framePath, value: result };
|
|
2072
|
+
}
|
|
2073
|
+
throw { code: 'E_NOT_FOUND', message: 'Unsupported page-world action: ' + action };
|
|
2074
|
+
};
|
|
2075
|
+
Promise.resolve()
|
|
2076
|
+
.then(async () => {
|
|
2077
|
+
const targets = buildScopeTargets();
|
|
2078
|
+
const results = [];
|
|
2079
|
+
for (const target of targets) {
|
|
2080
|
+
try {
|
|
2081
|
+
results.push(await toResult(target));
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
results.push({
|
|
2084
|
+
url: target.url,
|
|
2085
|
+
framePath: target.framePath,
|
|
2086
|
+
error: typeof error === 'object' && error !== null && 'code' in error
|
|
2087
|
+
? error
|
|
2088
|
+
: { code: 'E_EXECUTION', message: error instanceof Error ? error.message : String(error) }
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
if (payload.scope === 'all-frames') {
|
|
2093
|
+
emit({ ok: true, result: { scope: payload.scope, results } });
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
emit({ ok: true, result: { scope: payload.scope || 'current', result: results[0] } });
|
|
2097
|
+
})
|
|
2098
|
+
.catch((error) => {
|
|
2099
|
+
emit({
|
|
2100
|
+
ok: false,
|
|
2101
|
+
error: typeof error === 'object' && error !== null && 'code' in error
|
|
2102
|
+
? error
|
|
2103
|
+
: { code: 'E_EXECUTION', message: error instanceof Error ? error.message : String(error) }
|
|
2104
|
+
});
|
|
2105
|
+
});
|
|
2106
|
+
})();
|
|
2107
|
+
`;
|
|
2108
|
+
(document.documentElement ?? document.head ?? document.body).appendChild(injector);
|
|
2109
|
+
injector.remove();
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
function resolveScope(params) {
|
|
2113
|
+
const scope = typeof params.scope === "string" ? params.scope : "current";
|
|
2114
|
+
return scope === "main" || scope === "all-frames" ? scope : "current";
|
|
2115
|
+
}
|
|
2116
|
+
function pageWorldFramePath() {
|
|
2117
|
+
return [...contextState.framePath];
|
|
2118
|
+
}
|
|
2119
|
+
function pageEval(expr, params) {
|
|
2120
|
+
return runPageWorldRequest("eval", {
|
|
2121
|
+
expr,
|
|
2122
|
+
scope: resolveScope(params),
|
|
2123
|
+
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2124
|
+
framePath: pageWorldFramePath()
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
function pageExtract(path, params) {
|
|
2128
|
+
return runPageWorldRequest("extract", {
|
|
2129
|
+
path,
|
|
2130
|
+
resolver: typeof params.resolver === "string" ? params.resolver : void 0,
|
|
2131
|
+
scope: resolveScope(params),
|
|
2132
|
+
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2133
|
+
framePath: pageWorldFramePath()
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
function pageFetch(params) {
|
|
2137
|
+
return runPageWorldRequest("fetch", {
|
|
2138
|
+
url: String(params.url ?? ""),
|
|
2139
|
+
method: typeof params.method === "string" ? params.method : "GET",
|
|
2140
|
+
headers: typeof params.headers === "object" && params.headers !== null ? params.headers : void 0,
|
|
2141
|
+
body: typeof params.body === "string" ? params.body : void 0,
|
|
2142
|
+
contentType: typeof params.contentType === "string" ? params.contentType : void 0,
|
|
2143
|
+
mode: params.mode === "json" ? "json" : "raw",
|
|
2144
|
+
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2145
|
+
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : void 0,
|
|
2146
|
+
scope: resolveScope(params),
|
|
2147
|
+
framePath: pageWorldFramePath()
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
async function globalsPreview() {
|
|
2151
|
+
return Object.keys(window).filter((key) => DISCOVERY_GLOBAL_PATTERN.test(key)).slice(0, 50);
|
|
2152
|
+
}
|
|
2153
|
+
async function collectInspectionState(params = {}) {
|
|
2154
|
+
const rootResult = resolveRootForLocator();
|
|
2155
|
+
if (!rootResult.ok) {
|
|
2156
|
+
throw rootResult.error;
|
|
2157
|
+
}
|
|
2158
|
+
const root = rootResult.root;
|
|
2159
|
+
const metadata = documentMetadata(root, document);
|
|
2160
|
+
const visibleText = pageTextChunks(root, 40, 320);
|
|
2161
|
+
const combinedText = visibleText.map((chunk) => chunk.text).join("\n");
|
|
2162
|
+
const scripts = listInlineScripts();
|
|
2163
|
+
const visibleTimestampCandidates = timestampCandidateMatchesFromText(
|
|
2164
|
+
combinedText,
|
|
2165
|
+
Array.isArray(params.patterns) ? params.patterns.map(String) : void 0
|
|
2166
|
+
);
|
|
2167
|
+
const inlineTimestampCandidates = timestampCandidateMatchesFromText(
|
|
2168
|
+
scripts.map((script) => script.content).join("\n"),
|
|
2169
|
+
Array.isArray(params.patterns) ? params.patterns.map(String) : void 0
|
|
2170
|
+
);
|
|
2171
|
+
const previewGlobals = await globalsPreview();
|
|
2172
|
+
const suspiciousGlobals = [...new Set(scripts.flatMap((script) => script.suspectedVars))].slice(0, 50);
|
|
2173
|
+
return {
|
|
2174
|
+
url: metadata.url,
|
|
2175
|
+
title: metadata.title,
|
|
2176
|
+
html: redactHtmlSnapshot(document.documentElement),
|
|
2177
|
+
visibleText,
|
|
2178
|
+
visibleTimestamps: visibleTimestampCandidates.map((candidate) => candidate.value),
|
|
2179
|
+
visibleTimestampCandidates,
|
|
2180
|
+
inlineTimestamps: inlineTimestampCandidates.map((candidate) => candidate.value),
|
|
2181
|
+
inlineTimestampCandidates,
|
|
2182
|
+
suspiciousGlobals,
|
|
2183
|
+
globalsPreview: previewGlobals,
|
|
2184
|
+
scripts: {
|
|
2185
|
+
inlineCount: scripts.length,
|
|
2186
|
+
suspectedDataVars: suspiciousGlobals
|
|
2187
|
+
},
|
|
2188
|
+
storage: storageMetadata(),
|
|
2189
|
+
cookies: cookieMetadata(),
|
|
2190
|
+
frames: collectFrames(),
|
|
2191
|
+
tables: describeTables(),
|
|
2192
|
+
timers: {
|
|
2193
|
+
timeouts: 0,
|
|
2194
|
+
intervals: 0
|
|
2195
|
+
},
|
|
2196
|
+
pageLoadedAt,
|
|
2197
|
+
lastMutationAt,
|
|
2198
|
+
context: currentContextSnapshot()
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
1403
2201
|
function performDoubleClick(target, point) {
|
|
1404
2202
|
performClick(target, point);
|
|
1405
2203
|
performClick(target, point);
|
|
@@ -1667,6 +2465,12 @@
|
|
|
1667
2465
|
)
|
|
1668
2466
|
};
|
|
1669
2467
|
}
|
|
2468
|
+
case "page.eval":
|
|
2469
|
+
return await pageEval(String(params.expr ?? ""), params);
|
|
2470
|
+
case "page.extract":
|
|
2471
|
+
return await pageExtract(String(params.path ?? ""), params);
|
|
2472
|
+
case "page.fetch":
|
|
2473
|
+
return await pageFetch(params);
|
|
1670
2474
|
case "page.dom": {
|
|
1671
2475
|
const rootResult = resolveRootForLocator();
|
|
1672
2476
|
if (!rootResult.ok) {
|
|
@@ -1818,6 +2622,7 @@
|
|
|
1818
2622
|
if (!target) {
|
|
1819
2623
|
throw notFoundForLocator(params.locator, "element.get target not found");
|
|
1820
2624
|
}
|
|
2625
|
+
const matches = matchedElementsForLocator(params.locator);
|
|
1821
2626
|
const elements = collectElements({}, params.locator);
|
|
1822
2627
|
const eid = buildEid(target);
|
|
1823
2628
|
const element = elements.find((item) => item.eid === eid);
|
|
@@ -1830,6 +2635,10 @@
|
|
|
1830
2635
|
}
|
|
1831
2636
|
return {
|
|
1832
2637
|
element,
|
|
2638
|
+
matchedCount: matches.length,
|
|
2639
|
+
visible: isElementVisible(target),
|
|
2640
|
+
enabled: !(target.disabled || target.getAttribute("aria-disabled") === "true"),
|
|
2641
|
+
textPreview: (target.innerText || target.textContent || "").replace(/\s+/g, " ").trim().slice(0, 160),
|
|
1833
2642
|
value: isEditable(target) ? target.value : target.isContentEditable ? target.textContent ?? "" : void 0,
|
|
1834
2643
|
checked: isInputElement(target) ? target.checked : void 0,
|
|
1835
2644
|
attributes
|
|
@@ -2063,7 +2872,7 @@
|
|
|
2063
2872
|
if (!found) {
|
|
2064
2873
|
throw { code: "E_NOT_FOUND", message: `network entry not found: ${id}` };
|
|
2065
2874
|
}
|
|
2066
|
-
return { entry: found };
|
|
2875
|
+
return { entry: filterNetworkEntrySections(found, params.include) };
|
|
2067
2876
|
}
|
|
2068
2877
|
case "network.waitFor":
|
|
2069
2878
|
return { entry: await waitForNetwork(params) };
|
|
@@ -2071,6 +2880,34 @@
|
|
|
2071
2880
|
networkEntries.length = 0;
|
|
2072
2881
|
performanceBaselineMs = performance.now();
|
|
2073
2882
|
return { ok: true };
|
|
2883
|
+
case "table.list":
|
|
2884
|
+
return { tables: describeTables() };
|
|
2885
|
+
case "table.schema": {
|
|
2886
|
+
const resolved = resolveTable(String(params.table ?? ""));
|
|
2887
|
+
if (!resolved || !(resolved.element instanceof Element)) {
|
|
2888
|
+
throw { code: "E_NOT_FOUND", message: `table not found: ${String(params.table ?? "")}` };
|
|
2889
|
+
}
|
|
2890
|
+
const schema = resolved.element instanceof HTMLTableElement ? { columns: htmlTableSchema(resolved.element) } : { columns: gridSchema(resolved.element) };
|
|
2891
|
+
return {
|
|
2892
|
+
table: resolved.table,
|
|
2893
|
+
schema
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
case "table.rows":
|
|
2897
|
+
case "table.export": {
|
|
2898
|
+
const resolved = resolveTable(String(params.table ?? ""));
|
|
2899
|
+
if (!resolved || !(resolved.element instanceof Element)) {
|
|
2900
|
+
throw { code: "E_NOT_FOUND", message: `table not found: ${String(params.table ?? "")}` };
|
|
2901
|
+
}
|
|
2902
|
+
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;
|
|
2903
|
+
const extractionMode = resolved.table.kind === "html" || resolved.table.kind === "dataTables" ? "dataSource" : "visibleOnly";
|
|
2904
|
+
const rows = resolved.element instanceof HTMLTableElement ? htmlTableRows(resolved.element, requestedLimit) : gridRows(resolved.element, requestedLimit);
|
|
2905
|
+
return {
|
|
2906
|
+
table: resolved.table,
|
|
2907
|
+
extractionMode,
|
|
2908
|
+
rows
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2074
2911
|
case "debug.dumpState": {
|
|
2075
2912
|
const consoleLimit = typeof params.consoleLimit === "number" ? Math.max(1, Math.floor(params.consoleLimit)) : 80;
|
|
2076
2913
|
const networkLimit = typeof params.networkLimit === "number" ? Math.max(1, Math.floor(params.networkLimit)) : 80;
|
|
@@ -2080,6 +2917,7 @@
|
|
|
2080
2917
|
throw rootResult.error;
|
|
2081
2918
|
}
|
|
2082
2919
|
const metadata = documentMetadata(rootResult.root, document);
|
|
2920
|
+
const inspection = await collectInspectionState(params);
|
|
2083
2921
|
return {
|
|
2084
2922
|
url: metadata.url,
|
|
2085
2923
|
title: metadata.title,
|
|
@@ -2098,9 +2936,19 @@
|
|
|
2098
2936
|
},
|
|
2099
2937
|
console: consoleEntries.slice(-consoleLimit),
|
|
2100
2938
|
network: filterNetworkEntries({ limit: networkLimit }),
|
|
2101
|
-
accessibility: includeAccessibility ? pageAccessibility(rootResult.root, 200) : void 0
|
|
2939
|
+
accessibility: includeAccessibility ? pageAccessibility(rootResult.root, 200) : void 0,
|
|
2940
|
+
scripts: inspection.scripts,
|
|
2941
|
+
globalsPreview: inspection.globalsPreview,
|
|
2942
|
+
storage: inspection.storage,
|
|
2943
|
+
frames: inspection.frames,
|
|
2944
|
+
networkSummary: {
|
|
2945
|
+
total: filterNetworkEntries({ limit: networkLimit }).length,
|
|
2946
|
+
recent: filterNetworkEntries({ limit: Math.min(networkLimit, 10) })
|
|
2947
|
+
}
|
|
2102
2948
|
};
|
|
2103
2949
|
}
|
|
2950
|
+
case "bak.internal.inspectState":
|
|
2951
|
+
return await collectInspectionState(params);
|
|
2104
2952
|
default:
|
|
2105
2953
|
throw { code: "E_NOT_FOUND", message: `Unsupported content RPC method: ${method}` };
|
|
2106
2954
|
}
|
|
@@ -2153,4 +3001,5 @@
|
|
|
2153
3001
|
return false;
|
|
2154
3002
|
});
|
|
2155
3003
|
ensureOverlayRoot();
|
|
3004
|
+
trackMutations();
|
|
2156
3005
|
})();
|