@flrande/bak-extension 0.6.11 → 0.6.13
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 +588 -90
- package/dist/content.global.js +530 -12
- package/dist/manifest.json +1 -1
- package/dist/popup.global.js +233 -101
- package/dist/popup.html +260 -37
- package/package.json +2 -2
- package/public/popup.html +260 -37
- package/src/background.ts +344 -296
- package/src/content.ts +390 -22
- package/src/dynamic-data-tools.ts +790 -0
- package/src/popup.ts +291 -108
package/dist/content.global.js
CHANGED
|
@@ -30,6 +30,215 @@
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// src/dynamic-data-tools.ts
|
|
34
|
+
var DATA_PATTERN = /\b(updated|update|updatedat|asof|timestamp|generated|generatedat|refresh|freshness|latest|last|quote|trade|price|flow|market|time|snapshot|signal)\b/i;
|
|
35
|
+
var CONTRACT_PATTERN = /\b(expiry|expiration|expires|option|contract|strike|maturity|dte|call|put|exercise)\b/i;
|
|
36
|
+
var EVENT_PATTERN = /\b(earnings|event|report|dividend|split|meeting|fomc|release|filing)\b/i;
|
|
37
|
+
var ROW_CANDIDATE_KEYS = ["data", "rows", "results", "items", "records", "entries"];
|
|
38
|
+
function sampleValue(value, depth = 0) {
|
|
39
|
+
if (depth >= 2 || value === null || value === void 0 || typeof value !== "object") {
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
return value.length > 160 ? value.slice(0, 160) : value;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "function") {
|
|
44
|
+
return "[Function]";
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
return value.slice(0, 3).map((item) => sampleValue(item, depth + 1));
|
|
50
|
+
}
|
|
51
|
+
const sampled = {};
|
|
52
|
+
for (const key of Object.keys(value).slice(0, 8)) {
|
|
53
|
+
try {
|
|
54
|
+
sampled[key] = sampleValue(value[key], depth + 1);
|
|
55
|
+
} catch {
|
|
56
|
+
sampled[key] = "[Unreadable]";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return sampled;
|
|
60
|
+
}
|
|
61
|
+
function estimateSampleSize(value) {
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
return value.length;
|
|
64
|
+
}
|
|
65
|
+
if (value && typeof value === "object") {
|
|
66
|
+
return Object.keys(value).length;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function classifyTimestamp(path, value, now = Date.now()) {
|
|
71
|
+
const normalized = path.toLowerCase();
|
|
72
|
+
if (DATA_PATTERN.test(normalized)) {
|
|
73
|
+
return "data";
|
|
74
|
+
}
|
|
75
|
+
if (CONTRACT_PATTERN.test(normalized)) {
|
|
76
|
+
return "contract";
|
|
77
|
+
}
|
|
78
|
+
if (EVENT_PATTERN.test(normalized)) {
|
|
79
|
+
return "event";
|
|
80
|
+
}
|
|
81
|
+
const parsed = Date.parse(value.trim());
|
|
82
|
+
return Number.isFinite(parsed) && parsed > now + 36 * 60 * 60 * 1e3 ? "contract" : "unknown";
|
|
83
|
+
}
|
|
84
|
+
function collectTimestampProbes(value, path, options = {}) {
|
|
85
|
+
const collected = [];
|
|
86
|
+
const now = typeof options.now === "number" ? options.now : Date.now();
|
|
87
|
+
const limit = typeof options.limit === "number" ? Math.max(1, Math.floor(options.limit)) : 16;
|
|
88
|
+
const visit = (candidate, candidatePath, depth) => {
|
|
89
|
+
if (collected.length >= limit) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
93
|
+
const parsed = Date.parse(candidate.trim());
|
|
94
|
+
if (Number.isFinite(parsed)) {
|
|
95
|
+
collected.push({
|
|
96
|
+
path: candidatePath,
|
|
97
|
+
value: candidate,
|
|
98
|
+
category: classifyTimestamp(candidatePath, candidate, now)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (depth >= 3 || candidate === null || candidate === void 0) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(candidate)) {
|
|
107
|
+
candidate.slice(0, 3).forEach((entry, index) => visit(entry, `${candidatePath}[${index}]`, depth + 1));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (typeof candidate === "object") {
|
|
111
|
+
Object.keys(candidate).slice(0, 8).forEach((key) => {
|
|
112
|
+
try {
|
|
113
|
+
visit(candidate[key], candidatePath ? `${candidatePath}.${key}` : key, depth + 1);
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
visit(value, path, 0);
|
|
120
|
+
return collected;
|
|
121
|
+
}
|
|
122
|
+
function inferSchemaHint(value) {
|
|
123
|
+
const rowsCandidate = extractStructuredRows(value);
|
|
124
|
+
if (rowsCandidate) {
|
|
125
|
+
if (rowsCandidate.rowType === "object") {
|
|
126
|
+
const firstRecord = rowsCandidate.rows.find(
|
|
127
|
+
(row) => typeof row === "object" && row !== null && !Array.isArray(row)
|
|
128
|
+
);
|
|
129
|
+
return {
|
|
130
|
+
kind: "rows-object",
|
|
131
|
+
columns: firstRecord ? Object.keys(firstRecord).slice(0, 12) : []
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (rowsCandidate.rowType === "array") {
|
|
135
|
+
const firstRow = rowsCandidate.rows.find((row) => Array.isArray(row));
|
|
136
|
+
return {
|
|
137
|
+
kind: "rows-array",
|
|
138
|
+
columns: firstRow ? firstRow.map((_, index) => `Column ${index + 1}`) : []
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
return { kind: "array" };
|
|
144
|
+
}
|
|
145
|
+
if (value && typeof value === "object") {
|
|
146
|
+
return {
|
|
147
|
+
kind: "object",
|
|
148
|
+
columns: Object.keys(value).slice(0, 12)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (value === null || value === void 0) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return { kind: "scalar" };
|
|
155
|
+
}
|
|
156
|
+
function extractStructuredRows(value, path = "$") {
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
if (value.length === 0) {
|
|
159
|
+
return { rows: value, path, rowType: "object" };
|
|
160
|
+
}
|
|
161
|
+
const first = value.find((item) => item !== null && item !== void 0);
|
|
162
|
+
if (Array.isArray(first)) {
|
|
163
|
+
return { rows: value, path, rowType: "array" };
|
|
164
|
+
}
|
|
165
|
+
if (first && typeof first === "object") {
|
|
166
|
+
return { rows: value, path, rowType: "object" };
|
|
167
|
+
}
|
|
168
|
+
return { rows: value, path, rowType: "scalar" };
|
|
169
|
+
}
|
|
170
|
+
if (!value || typeof value !== "object") {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const record = value;
|
|
174
|
+
for (const key of ROW_CANDIDATE_KEYS) {
|
|
175
|
+
if (Array.isArray(record[key])) {
|
|
176
|
+
return extractStructuredRows(record[key], `${path}.${key}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
function buildTableIntelligence(input) {
|
|
182
|
+
const signals = [];
|
|
183
|
+
const visibleRowCount = Math.max(0, Math.floor(input.visibleRowCount));
|
|
184
|
+
const estimatedTotalRows = typeof input.estimatedTotalRows === "number" && Number.isFinite(input.estimatedTotalRows) && input.estimatedTotalRows > 0 ? Math.max(visibleRowCount, Math.floor(input.estimatedTotalRows)) : void 0;
|
|
185
|
+
const maxObservedRowIndex = typeof input.maxObservedRowIndex === "number" && Number.isFinite(input.maxObservedRowIndex) ? Math.max(0, Math.floor(input.maxObservedRowIndex)) : void 0;
|
|
186
|
+
const minObservedRowIndex = typeof input.minObservedRowIndex === "number" && Number.isFinite(input.minObservedRowIndex) ? Math.max(0, Math.floor(input.minObservedRowIndex)) : void 0;
|
|
187
|
+
if (input.knownGridKind) {
|
|
188
|
+
signals.push({
|
|
189
|
+
code: "known-grid-kind",
|
|
190
|
+
detail: `Detected ${input.kind} container semantics`
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (input.hasScrollContainer) {
|
|
194
|
+
signals.push({
|
|
195
|
+
code: "scroll-container",
|
|
196
|
+
detail: "Scrollable container detected for the table region"
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (input.hasTranslatedRows) {
|
|
200
|
+
signals.push({
|
|
201
|
+
code: "row-transform-offsets",
|
|
202
|
+
detail: "Row transform offsets indicate viewport-based row reuse"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (maxObservedRowIndex !== void 0 && maxObservedRowIndex > visibleRowCount) {
|
|
206
|
+
signals.push({
|
|
207
|
+
code: "row-index-gap",
|
|
208
|
+
detail: `Observed row indexes reach ${maxObservedRowIndex} while only ${visibleRowCount} rows are mounted`
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (estimatedTotalRows !== void 0 && estimatedTotalRows > visibleRowCount) {
|
|
212
|
+
signals.push({
|
|
213
|
+
code: "dom-rows-less-than-expected",
|
|
214
|
+
detail: `Estimated ${estimatedTotalRows} rows with ${visibleRowCount} currently mounted`
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const virtualized = input.hasTranslatedRows || input.hasScrollContainer && (estimatedTotalRows !== void 0 && estimatedTotalRows > visibleRowCount || maxObservedRowIndex !== void 0 && maxObservedRowIndex > visibleRowCount + 1);
|
|
218
|
+
const lazyLoaded = input.hasScrollContainer && !virtualized && estimatedTotalRows !== void 0 && estimatedTotalRows > visibleRowCount;
|
|
219
|
+
const preferredExtractionMode = input.kind === "html" || input.kind === "dataTables" ? "dataSource" : input.hasScrollContainer ? "scroll" : "visibleOnly";
|
|
220
|
+
const completeness = preferredExtractionMode === "dataSource" ? "complete" : estimatedTotalRows !== void 0 && estimatedTotalRows > visibleRowCount ? "partial" : minObservedRowIndex !== void 0 && minObservedRowIndex > 1 ? "partial" : "unknown";
|
|
221
|
+
return {
|
|
222
|
+
virtualized,
|
|
223
|
+
lazyLoaded,
|
|
224
|
+
preferredExtractionMode,
|
|
225
|
+
estimatedTotalRows,
|
|
226
|
+
completeness,
|
|
227
|
+
signals
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function buildExtractionMetadata(mode, rows, intelligence, warnings = [], options = {}) {
|
|
231
|
+
const estimatedTotalRows = intelligence?.estimatedTotalRows;
|
|
232
|
+
const complete = options.limitApplied ? false : mode === "dataSource" ? true : options.reachedEnd === true ? true : intelligence?.completeness === "complete";
|
|
233
|
+
return {
|
|
234
|
+
mode,
|
|
235
|
+
complete: complete === true,
|
|
236
|
+
observedRows: rows.length,
|
|
237
|
+
estimatedTotalRows,
|
|
238
|
+
warnings
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
33
242
|
// src/privacy.ts
|
|
34
243
|
var MAX_SAFE_TEXT_LENGTH = 120;
|
|
35
244
|
var MAX_DEBUG_TEXT_LENGTH = 320;
|
|
@@ -1676,6 +1885,111 @@
|
|
|
1676
1885
|
function buildTableId(kind, index) {
|
|
1677
1886
|
return `${kind}:${index + 1}`;
|
|
1678
1887
|
}
|
|
1888
|
+
function gridRowNodes(grid) {
|
|
1889
|
+
return Array.from(grid.querySelectorAll('[role="row"]')).filter(
|
|
1890
|
+
(row) => row.querySelector('[role="gridcell"], [role="cell"]') !== null
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
function tableSchemaFromElement(element) {
|
|
1894
|
+
return element instanceof HTMLTableElement ? htmlTableSchema(element) : gridSchema(element);
|
|
1895
|
+
}
|
|
1896
|
+
function tableRowsFromElement(element, limit) {
|
|
1897
|
+
return element instanceof HTMLTableElement ? htmlTableRows(element, limit) : gridRows(element, limit);
|
|
1898
|
+
}
|
|
1899
|
+
function findScrollableTableContainer(element) {
|
|
1900
|
+
const explicitCandidate = isHtmlElement(element) && (element.matches("[data-bak-scroll-root], [data-virtual-scroll]") ? element : null) || ("querySelector" in element ? element.querySelector("[data-bak-scroll-root], [data-virtual-scroll]") ?? null : null);
|
|
1901
|
+
if (explicitCandidate) {
|
|
1902
|
+
return explicitCandidate;
|
|
1903
|
+
}
|
|
1904
|
+
const candidates = [];
|
|
1905
|
+
if (isHtmlElement(element)) {
|
|
1906
|
+
candidates.push(element);
|
|
1907
|
+
}
|
|
1908
|
+
if ("querySelectorAll" in element) {
|
|
1909
|
+
candidates.push(...Array.from(element.querySelectorAll('[role="rowgroup"], *')));
|
|
1910
|
+
}
|
|
1911
|
+
let current = element.parentElement;
|
|
1912
|
+
while (current && candidates.length < 80) {
|
|
1913
|
+
candidates.push(current);
|
|
1914
|
+
current = current.parentElement;
|
|
1915
|
+
}
|
|
1916
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
1917
|
+
return uniqueCandidates.find((candidate) => {
|
|
1918
|
+
const style = getComputedStyle(candidate);
|
|
1919
|
+
return (style.overflowY === "auto" || style.overflowY === "scroll") && candidate.scrollHeight > candidate.clientHeight + 4 && candidate.clientHeight > 0;
|
|
1920
|
+
}) ?? null;
|
|
1921
|
+
}
|
|
1922
|
+
function observedRowIndexes(rows) {
|
|
1923
|
+
return rows.map((row) => {
|
|
1924
|
+
const raw = row.getAttribute("aria-rowindex") || row.getAttribute("data-row-index") || row.dataset.rowIndex;
|
|
1925
|
+
const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
1926
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
1927
|
+
}).filter((value) => value !== null);
|
|
1928
|
+
}
|
|
1929
|
+
function inferEstimatedTotalRows(element, rows, scrollContainer) {
|
|
1930
|
+
const attrCandidates = [
|
|
1931
|
+
element.getAttribute("aria-rowcount"),
|
|
1932
|
+
(scrollContainer ?? void 0)?.getAttribute("aria-rowcount"),
|
|
1933
|
+
element.getAttribute("data-total-rows"),
|
|
1934
|
+
(scrollContainer ?? void 0)?.getAttribute("data-total-rows")
|
|
1935
|
+
].filter((candidate) => typeof candidate === "string" && candidate.trim().length > 0).map((candidate) => Number.parseInt(candidate, 10)).filter((candidate) => Number.isFinite(candidate) && candidate > 0);
|
|
1936
|
+
if (attrCandidates.length > 0) {
|
|
1937
|
+
return Math.max(...attrCandidates);
|
|
1938
|
+
}
|
|
1939
|
+
const indexes = observedRowIndexes(rows);
|
|
1940
|
+
if (indexes.length > 0) {
|
|
1941
|
+
return Math.max(...indexes);
|
|
1942
|
+
}
|
|
1943
|
+
const rowHeight = rows[0]?.getBoundingClientRect().height ?? 0;
|
|
1944
|
+
if (scrollContainer && rowHeight > 0) {
|
|
1945
|
+
const estimate = Math.round(scrollContainer.scrollHeight / rowHeight);
|
|
1946
|
+
if (estimate > rows.length) {
|
|
1947
|
+
return estimate;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
return void 0;
|
|
1951
|
+
}
|
|
1952
|
+
function tableIntelligenceForElement(table, element) {
|
|
1953
|
+
if (element instanceof HTMLTableElement) {
|
|
1954
|
+
return buildTableIntelligence({
|
|
1955
|
+
kind: table.kind,
|
|
1956
|
+
visibleRowCount: htmlTableRows(element, Number.MAX_SAFE_INTEGER).length,
|
|
1957
|
+
rowCount: table.rowCount,
|
|
1958
|
+
estimatedTotalRows: table.rowCount,
|
|
1959
|
+
hasScrollContainer: false,
|
|
1960
|
+
hasTranslatedRows: false,
|
|
1961
|
+
knownGridKind: false
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
const rows = gridRowNodes(element);
|
|
1965
|
+
const scrollContainer = findScrollableTableContainer(element);
|
|
1966
|
+
const rowIndexes = observedRowIndexes(rows);
|
|
1967
|
+
const estimatedTotalRows = inferEstimatedTotalRows(element, rows, scrollContainer);
|
|
1968
|
+
const hasTranslatedRows = rows.some((row) => {
|
|
1969
|
+
const style = row.style.transform || getComputedStyle(row).transform;
|
|
1970
|
+
return typeof style === "string" && style !== "" && style !== "none";
|
|
1971
|
+
});
|
|
1972
|
+
return buildTableIntelligence({
|
|
1973
|
+
kind: table.kind,
|
|
1974
|
+
visibleRowCount: rows.length,
|
|
1975
|
+
rowCount: table.rowCount,
|
|
1976
|
+
estimatedTotalRows,
|
|
1977
|
+
hasScrollContainer: scrollContainer !== null,
|
|
1978
|
+
hasTranslatedRows,
|
|
1979
|
+
maxObservedRowIndex: rowIndexes.length > 0 ? Math.max(...rowIndexes) : void 0,
|
|
1980
|
+
minObservedRowIndex: rowIndexes.length > 0 ? Math.min(...rowIndexes) : void 0,
|
|
1981
|
+
knownGridKind: table.kind !== "aria-grid"
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
function withTableIntelligence(table, element) {
|
|
1985
|
+
if (!(element instanceof Element)) {
|
|
1986
|
+
return table;
|
|
1987
|
+
}
|
|
1988
|
+
return {
|
|
1989
|
+
...table,
|
|
1990
|
+
intelligence: tableIntelligenceForElement(table, element)
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1679
1993
|
function describeTables() {
|
|
1680
1994
|
const rootResult = resolveRootForLocator();
|
|
1681
1995
|
if (!rootResult.ok) {
|
|
@@ -1685,26 +1999,28 @@
|
|
|
1685
1999
|
const tables = [];
|
|
1686
2000
|
const htmlTables = Array.from(root.querySelectorAll("table"));
|
|
1687
2001
|
for (const [index, table] of htmlTables.entries()) {
|
|
1688
|
-
|
|
2002
|
+
const handle = {
|
|
1689
2003
|
id: buildTableId(table.closest(".dataTables_wrapper") ? "dataTables" : "html", index),
|
|
1690
2004
|
name: (table.getAttribute("aria-label") || table.getAttribute("data-testid") || table.id || `table-${index + 1}`).trim(),
|
|
1691
2005
|
kind: table.closest(".dataTables_wrapper") ? "dataTables" : "html",
|
|
1692
2006
|
selector: table.id ? `#${table.id}` : void 0,
|
|
1693
2007
|
rowCount: table.querySelectorAll("tbody tr").length || table.querySelectorAll("tr").length,
|
|
1694
2008
|
columnCount: table.querySelectorAll("thead th").length || table.querySelectorAll("tr:first-child th, tr:first-child td").length
|
|
1695
|
-
}
|
|
2009
|
+
};
|
|
2010
|
+
tables.push(withTableIntelligence(handle, table));
|
|
1696
2011
|
}
|
|
1697
2012
|
const gridRoots = Array.from(root.querySelectorAll('[role="grid"], [role="table"], .ag-root, .ag-root-wrapper'));
|
|
1698
2013
|
for (const [index, grid] of gridRoots.entries()) {
|
|
1699
2014
|
const kind = grid.className.includes("ag-") ? "ag-grid" : "aria-grid";
|
|
1700
|
-
|
|
2015
|
+
const handle = {
|
|
1701
2016
|
id: buildTableId(kind, index),
|
|
1702
2017
|
name: (grid.getAttribute("aria-label") || grid.getAttribute("data-testid") || grid.id || `grid-${index + 1}`).trim(),
|
|
1703
2018
|
kind,
|
|
1704
2019
|
selector: grid.id ? `#${grid.id}` : void 0,
|
|
1705
|
-
rowCount: grid
|
|
2020
|
+
rowCount: gridRowNodes(grid).length,
|
|
1706
2021
|
columnCount: grid.querySelectorAll('[role="columnheader"]').length
|
|
1707
|
-
}
|
|
2022
|
+
};
|
|
2023
|
+
tables.push(withTableIntelligence(handle, grid));
|
|
1708
2024
|
}
|
|
1709
2025
|
return tables;
|
|
1710
2026
|
}
|
|
@@ -1764,15 +2080,171 @@
|
|
|
1764
2080
|
}
|
|
1765
2081
|
function gridRows(grid, limit) {
|
|
1766
2082
|
const columns = gridSchema(grid);
|
|
1767
|
-
return
|
|
2083
|
+
return gridRowNodes(grid).slice(0, limit).map((row) => {
|
|
1768
2084
|
const record = {};
|
|
1769
|
-
Array.from(row.querySelectorAll('[role="gridcell"], [role="cell"]
|
|
2085
|
+
Array.from(row.querySelectorAll('[role="gridcell"], [role="cell"]')).forEach((cell, index) => {
|
|
1770
2086
|
const key = columns[index]?.label ?? `Column ${index + 1}`;
|
|
1771
2087
|
record[key] = (cell.textContent ?? "").trim();
|
|
1772
2088
|
});
|
|
1773
2089
|
return record;
|
|
1774
2090
|
}).filter((row) => Object.values(row).some((value) => String(value).trim().length > 0));
|
|
1775
2091
|
}
|
|
2092
|
+
function rowSignature(row) {
|
|
2093
|
+
return JSON.stringify(
|
|
2094
|
+
Object.entries(row).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => [key, value === void 0 ? null : value])
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
function mergeUniqueRows(target, rows, limit) {
|
|
2098
|
+
let added = 0;
|
|
2099
|
+
for (const row of rows) {
|
|
2100
|
+
const signature = rowSignature(row);
|
|
2101
|
+
if (target.has(signature)) {
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
target.set(signature, row);
|
|
2105
|
+
added += 1;
|
|
2106
|
+
if (target.size >= limit) {
|
|
2107
|
+
break;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
return added;
|
|
2111
|
+
}
|
|
2112
|
+
function waitForPaint(frames = 2) {
|
|
2113
|
+
return new Promise((resolve) => {
|
|
2114
|
+
const schedule = (remaining) => {
|
|
2115
|
+
if (remaining <= 0) {
|
|
2116
|
+
window.setTimeout(() => resolve(), 24);
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
requestAnimationFrame(() => schedule(remaining - 1));
|
|
2120
|
+
};
|
|
2121
|
+
schedule(frames);
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
function currentTableWindowSignature(element) {
|
|
2125
|
+
const rowIndexes = observedRowIndexes(gridRowNodes(element));
|
|
2126
|
+
if (rowIndexes.length > 0) {
|
|
2127
|
+
return `indexes:${rowIndexes.join(",")}`;
|
|
2128
|
+
}
|
|
2129
|
+
const visibleRows = tableRowsFromElement(element, 24);
|
|
2130
|
+
return `rows:${visibleRows.map((row) => rowSignature(row)).join("|")}`;
|
|
2131
|
+
}
|
|
2132
|
+
async function waitForTableWindowChange(element, previousSignature, timeoutMs = 200) {
|
|
2133
|
+
const start = Date.now();
|
|
2134
|
+
let currentSignature = previousSignature;
|
|
2135
|
+
while (Date.now() - start < timeoutMs) {
|
|
2136
|
+
await waitForPaint(1);
|
|
2137
|
+
currentSignature = currentTableWindowSignature(element);
|
|
2138
|
+
if (currentSignature !== previousSignature) {
|
|
2139
|
+
return currentSignature;
|
|
2140
|
+
}
|
|
2141
|
+
await new Promise((resolve) => window.setTimeout(resolve, 16));
|
|
2142
|
+
}
|
|
2143
|
+
return currentSignature;
|
|
2144
|
+
}
|
|
2145
|
+
async function scrollExtractRows(element, limit, intelligence) {
|
|
2146
|
+
const scrollContainer = findScrollableTableContainer(element);
|
|
2147
|
+
if (!scrollContainer) {
|
|
2148
|
+
const rows2 = tableRowsFromElement(element, limit);
|
|
2149
|
+
return {
|
|
2150
|
+
rows: rows2,
|
|
2151
|
+
extractionMode: "visibleOnly",
|
|
2152
|
+
extraction: buildExtractionMetadata("visibleOnly", rows2, intelligence, [
|
|
2153
|
+
"Fell back to visible rows because no scroll container was detected."
|
|
2154
|
+
])
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
const originalScrollTop = scrollContainer.scrollTop;
|
|
2158
|
+
const collected = /* @__PURE__ */ new Map();
|
|
2159
|
+
const seenRowIndexes = /* @__PURE__ */ new Set();
|
|
2160
|
+
let stagnantPasses = 0;
|
|
2161
|
+
let reachedEnd = false;
|
|
2162
|
+
let limitApplied = false;
|
|
2163
|
+
const rowHeight = gridRowNodes(element)[0]?.getBoundingClientRect().height ?? 0;
|
|
2164
|
+
const step = Math.max(80, Math.round(scrollContainer.clientHeight * 0.8), rowHeight > 0 ? Math.round(rowHeight * 4) : 0);
|
|
2165
|
+
const initialMaxScrollTop = Math.max(0, scrollContainer.scrollHeight - scrollContainer.clientHeight);
|
|
2166
|
+
const maxIterations = Math.max(8, Math.min(80, Math.ceil(initialMaxScrollTop / Math.max(step, 1)) + 6, limit * 2));
|
|
2167
|
+
try {
|
|
2168
|
+
scrollContainer.scrollTop = 0;
|
|
2169
|
+
scrollContainer.dispatchEvent(new Event("scroll"));
|
|
2170
|
+
await waitForPaint();
|
|
2171
|
+
let currentWindowSignature = currentTableWindowSignature(element);
|
|
2172
|
+
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
2173
|
+
const currentRows = tableRowsFromElement(element, limit);
|
|
2174
|
+
const added = mergeUniqueRows(collected, currentRows, limit);
|
|
2175
|
+
observedRowIndexes(gridRowNodes(element)).forEach((rowIndex) => {
|
|
2176
|
+
seenRowIndexes.add(rowIndex);
|
|
2177
|
+
});
|
|
2178
|
+
const estimatedTotalRows = intelligence?.estimatedTotalRows;
|
|
2179
|
+
if (typeof estimatedTotalRows === "number" && (collected.size >= estimatedTotalRows || seenRowIndexes.size >= estimatedTotalRows)) {
|
|
2180
|
+
reachedEnd = true;
|
|
2181
|
+
break;
|
|
2182
|
+
}
|
|
2183
|
+
if (collected.size >= limit) {
|
|
2184
|
+
limitApplied = true;
|
|
2185
|
+
break;
|
|
2186
|
+
}
|
|
2187
|
+
if (added === 0) {
|
|
2188
|
+
stagnantPasses += 1;
|
|
2189
|
+
} else {
|
|
2190
|
+
stagnantPasses = 0;
|
|
2191
|
+
}
|
|
2192
|
+
const maxScrollTop = Math.max(0, scrollContainer.scrollHeight - scrollContainer.clientHeight);
|
|
2193
|
+
if (scrollContainer.scrollTop >= maxScrollTop - 2) {
|
|
2194
|
+
reachedEnd = true;
|
|
2195
|
+
if (stagnantPasses >= 1) {
|
|
2196
|
+
break;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
if (stagnantPasses >= 2) {
|
|
2200
|
+
break;
|
|
2201
|
+
}
|
|
2202
|
+
const nextScrollTop = Math.min(maxScrollTop, scrollContainer.scrollTop + step);
|
|
2203
|
+
if (nextScrollTop === scrollContainer.scrollTop) {
|
|
2204
|
+
reachedEnd = true;
|
|
2205
|
+
break;
|
|
2206
|
+
}
|
|
2207
|
+
scrollContainer.scrollTop = nextScrollTop;
|
|
2208
|
+
scrollContainer.dispatchEvent(new Event("scroll"));
|
|
2209
|
+
currentWindowSignature = await waitForTableWindowChange(element, currentWindowSignature);
|
|
2210
|
+
}
|
|
2211
|
+
if (!reachedEnd) {
|
|
2212
|
+
const maxScrollTop = Math.max(0, scrollContainer.scrollHeight - scrollContainer.clientHeight);
|
|
2213
|
+
if (scrollContainer.scrollTop < maxScrollTop) {
|
|
2214
|
+
const previousWindowSignature = currentTableWindowSignature(element);
|
|
2215
|
+
scrollContainer.scrollTop = maxScrollTop;
|
|
2216
|
+
scrollContainer.dispatchEvent(new Event("scroll"));
|
|
2217
|
+
await waitForTableWindowChange(element, previousWindowSignature);
|
|
2218
|
+
mergeUniqueRows(collected, tableRowsFromElement(element, limit), limit);
|
|
2219
|
+
observedRowIndexes(gridRowNodes(element)).forEach((rowIndex) => {
|
|
2220
|
+
seenRowIndexes.add(rowIndex);
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
} finally {
|
|
2225
|
+
scrollContainer.scrollTop = originalScrollTop;
|
|
2226
|
+
await waitForPaint();
|
|
2227
|
+
}
|
|
2228
|
+
const rows = [...collected.values()].slice(0, limit);
|
|
2229
|
+
if (typeof intelligence?.estimatedTotalRows === "number" && (rows.length >= intelligence.estimatedTotalRows || seenRowIndexes.size >= intelligence.estimatedTotalRows)) {
|
|
2230
|
+
reachedEnd = true;
|
|
2231
|
+
}
|
|
2232
|
+
const warnings = [];
|
|
2233
|
+
if (limitApplied) {
|
|
2234
|
+
warnings.push("Stopped after reaching maxRows before confirming the end of the table.");
|
|
2235
|
+
}
|
|
2236
|
+
if (!reachedEnd && (intelligence?.estimatedTotalRows ?? rows.length) > rows.length) {
|
|
2237
|
+
warnings.push("Could not confirm the end of scrollable content; result may be partial.");
|
|
2238
|
+
}
|
|
2239
|
+
return {
|
|
2240
|
+
rows,
|
|
2241
|
+
extractionMode: "scroll",
|
|
2242
|
+
extraction: buildExtractionMetadata("scroll", rows, intelligence, warnings, {
|
|
2243
|
+
reachedEnd,
|
|
2244
|
+
limitApplied
|
|
2245
|
+
})
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
1776
2248
|
function runPageWorldRequest(action, payload) {
|
|
1777
2249
|
const requestId = `bak_page_world_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
1778
2250
|
return new Promise((resolve, reject) => {
|
|
@@ -2150,6 +2622,31 @@
|
|
|
2150
2622
|
async function globalsPreview() {
|
|
2151
2623
|
return Object.keys(window).filter((key) => DISCOVERY_GLOBAL_PATTERN.test(key)).slice(0, 50);
|
|
2152
2624
|
}
|
|
2625
|
+
function collectInlineJsonSources(root) {
|
|
2626
|
+
const scripts = Array.from(root.querySelectorAll('script[type="application/json"], script[type="application/ld+json"]'));
|
|
2627
|
+
return scripts.map((script, index) => {
|
|
2628
|
+
const raw = script.textContent?.trim() ?? "";
|
|
2629
|
+
if (!raw) {
|
|
2630
|
+
return null;
|
|
2631
|
+
}
|
|
2632
|
+
try {
|
|
2633
|
+
const parsed = JSON.parse(raw);
|
|
2634
|
+
const path = script.id ? `#${script.id}` : `inline-json:${index + 1}`;
|
|
2635
|
+
const timestamps = collectTimestampProbes(parsed, path);
|
|
2636
|
+
return {
|
|
2637
|
+
label: script.id || script.getAttribute("data-testid") || script.type || `inline-json-${index + 1}`,
|
|
2638
|
+
path,
|
|
2639
|
+
sample: sampleValue(parsed),
|
|
2640
|
+
sampleSize: estimateSampleSize(parsed),
|
|
2641
|
+
schemaHint: inferSchemaHint(parsed),
|
|
2642
|
+
lastObservedAt: timestamps.length > 0 ? Math.max(...timestamps.map((item) => Date.parse(item.value)).filter(Number.isFinite)) : null,
|
|
2643
|
+
timestamps
|
|
2644
|
+
};
|
|
2645
|
+
} catch {
|
|
2646
|
+
return null;
|
|
2647
|
+
}
|
|
2648
|
+
}).filter((item) => item !== null);
|
|
2649
|
+
}
|
|
2153
2650
|
async function collectInspectionState(params = {}) {
|
|
2154
2651
|
const rootResult = resolveRootForLocator();
|
|
2155
2652
|
if (!rootResult.ok) {
|
|
@@ -2170,6 +2667,7 @@
|
|
|
2170
2667
|
);
|
|
2171
2668
|
const previewGlobals = await globalsPreview();
|
|
2172
2669
|
const suspiciousGlobals = [...new Set(scripts.flatMap((script) => script.suspectedVars))].slice(0, 50);
|
|
2670
|
+
const inlineJsonSources = collectInlineJsonSources(root);
|
|
2173
2671
|
return {
|
|
2174
2672
|
url: metadata.url,
|
|
2175
2673
|
title: metadata.title,
|
|
@@ -2188,6 +2686,7 @@
|
|
|
2188
2686
|
storage: storageMetadata(),
|
|
2189
2687
|
cookies: cookieMetadata(),
|
|
2190
2688
|
frames: collectFrames(),
|
|
2689
|
+
inlineJsonSources,
|
|
2191
2690
|
tables: describeTables(),
|
|
2192
2691
|
timers: {
|
|
2193
2692
|
timeouts: 0,
|
|
@@ -2887,9 +3386,10 @@
|
|
|
2887
3386
|
if (!resolved || !(resolved.element instanceof Element)) {
|
|
2888
3387
|
throw { code: "E_NOT_FOUND", message: `table not found: ${String(params.table ?? "")}` };
|
|
2889
3388
|
}
|
|
2890
|
-
const
|
|
3389
|
+
const table = withTableIntelligence(resolved.table, resolved.element);
|
|
3390
|
+
const schema = { columns: tableSchemaFromElement(resolved.element) };
|
|
2891
3391
|
return {
|
|
2892
|
-
table
|
|
3392
|
+
table,
|
|
2893
3393
|
schema
|
|
2894
3394
|
};
|
|
2895
3395
|
}
|
|
@@ -2900,11 +3400,29 @@
|
|
|
2900
3400
|
throw { code: "E_NOT_FOUND", message: `table not found: ${String(params.table ?? "")}` };
|
|
2901
3401
|
}
|
|
2902
3402
|
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
|
|
2904
|
-
|
|
3403
|
+
const table = withTableIntelligence(resolved.table, resolved.element);
|
|
3404
|
+
let rows = tableRowsFromElement(resolved.element, requestedLimit);
|
|
3405
|
+
let extractionMode = table.intelligence?.preferredExtractionMode ?? (resolved.element instanceof HTMLTableElement ? "dataSource" : "visibleOnly");
|
|
3406
|
+
let extraction = buildExtractionMetadata(
|
|
3407
|
+
extractionMode,
|
|
3408
|
+
rows,
|
|
3409
|
+
table.intelligence,
|
|
3410
|
+
params.all === true && extractionMode === "visibleOnly" ? ["Only visible rows could be read from this table region."] : [],
|
|
3411
|
+
{
|
|
3412
|
+
reachedEnd: extractionMode === "dataSource",
|
|
3413
|
+
limitApplied: params.all === true && rows.length >= requestedLimit
|
|
3414
|
+
}
|
|
3415
|
+
);
|
|
3416
|
+
if (params.all === true && extractionMode === "scroll") {
|
|
3417
|
+
const scrolled = await scrollExtractRows(resolved.element, requestedLimit, table.intelligence);
|
|
3418
|
+
rows = scrolled.rows;
|
|
3419
|
+
extractionMode = scrolled.extractionMode;
|
|
3420
|
+
extraction = scrolled.extraction;
|
|
3421
|
+
}
|
|
2905
3422
|
return {
|
|
2906
|
-
table
|
|
3423
|
+
table,
|
|
2907
3424
|
extractionMode,
|
|
3425
|
+
extraction,
|
|
2908
3426
|
rows
|
|
2909
3427
|
};
|
|
2910
3428
|
}
|